DEV Community

Cover image for Interview Questions: Singleton Pattern vs the Static Keyword
Paula Fahmy
Paula Fahmy

Posted on • Edited on

Interview Questions: Singleton Pattern vs the Static Keyword

Introduction

If you've been in the tech industry for a while, you've certainly heard about the buzz words: "Design Patterns", maybe you've also heard about a famous pattern, the Singleton. Additionally, if you've been coding for a brief period, I'm hundred percent sure you've used the keyword static in one or more of your favorite programming languages.

For all of you experienced techies, you might already recognize that "Singletons" are used to create one and only one instance object of a class.

Hello World! once again, today with another interesting programming topic, let's discuss the differences between using the keyword static and implementing the Singleton Design Pattern.

Tech explanation aside, I want to quickly point out that I was hit by question lately while interviewing for a .NET Developer role in a software house, luckily I was prepared because it has popped into my mind a while ago before the interview itself.
Anyways, this is an interview question, so ladies and gentlemen, start taking notes.

I'm going to host my discussion in C# since it is a really powerful programming language, and it is in fact similar to other famous languages like Java and C++, just keep in mind that what's written in this blog should apply to any OOP language, so pick your favorite, and hop on.

The static Keyword

We normally use the static keyword (in C#) in order to declare:

  • Static Classes
  • Static Class Members, such as:
    • Constructors
    • Fields
    • Properties
    • Methods

This specific type of declaration is useful when only one copy of the object (class or class members) is needed that single instance is shared globally among all other objects, in other words the static variable belongs to the type itself rather than to a specific object created from that type.

Let me throw a real-life example to make things a little bit clearer.
I love Mustangs, the Ford cars of course (who doesn't!)...

Let's say we are developing a game, and we want to model this type of car on its own class, what are the properties that we should include?

I'm thinking:

  • Color
  • Body type (hatchback or regular)
  • Top Speed (could differ depending on the model)
  • First year of production (1965)
  • Year Model
  • Logo

Now, we know that "First year of production" for this type of car will always be 1965, also the "Logo", it's the same for all cars. Now let's say Ford decides to modify the logo for some reason, it should be changed for all cars, right?
These two variables are perfect candidates for static variables.
Other variables should depend on the car itself (the object) and could differ for every single one.

class Mustang 
{
    public string Color { get; set; }
    public bool IsHatchback { get; set; }
    public int TopSpeed { get; set; }
    public int YearModel { get; set; }
    public static int FirstYearInProduction { get; set; } = 1965;
    public static string LogoURL { get; set; } = "https://ford.com/logo.png";
}
Enter fullscreen mode Exit fullscreen mode

See how the compiler complains when trying to access the static variable from an instance object?

Which makes sense actually, why would I ask about the first production year using my car, or my friend's car, it's all the same at the end, right?

So in a way, FirstYearInProduction and LogoURL are being singleton-like in their nature, same value for all instances, right?

Like fields and properties, static methods also belong to the type itself.
.. and for the sake completeness of this article, let me list down the features of static classes in C#:

  • They can only contain static data members.
  • It is not allowed to create objects of the static class.
  • Static classes are sealed (could not be inherited from).

Okay, now that we've scratched the surface of the static keyword in most programming languages, now let's discuss what a Singleton Pattern means in order to formulate a rational comparison.

But wait, what are design patterns in the first place⁉️

Design Patterns

According to refactoring.guru:

Design patterns are typical solutions to common problems
in software design. Each pattern is like a blueprint
that you can customize to solve a particular
design problem in your code.

Design Patterns comes in different sizes and shapes, and they can be categorized into 3 main types:

  1. Creational: These patterns are designed for class instantiation. They can be either class-creation patterns or object-creational patterns.

  2. Structural: These patterns are designed with regard to a class's structure and composition. The main goal of most of these patterns is to increase the functionality of the class(es) involved, without changing much of its composition.

  3. Behavioral: These patterns are designed depending on how one class communicates with others.

Singleton Design Pattern

Now, let's get back to our main subject, the Singleton Pattern.

Singletons are not part of a specific language (not a keyword for example) or do they belong to frameworks. Singletons are just special classes built in a special way to fit in a special use case..
Lot of "specials", huh? That's the case with most modern design patterns.

The pattern's main objective is to create only one instance of a class and to provide only one global access point to that object.

As you might've guessed, this description fits nicely into the Creational patterns category, because at the end, we are "creating" an object, with only a "single" instance.

So filling in the gaps of Refactoring Guru's definition stated above, the current design problem at hand is that we need to create only one instance of the object, that's why we've chosen the Singleton Design Pattern.

Singleton Implementation in C#

Now I'm going to write down a couple of versions of Singletons in C#, let's start first by the simplest one, a Naïve one if you will, and I'm going to explain why it wouldn't hold up in tough environments in a moment.

Naïve Singleton

public sealed class NaiveSingleton
{
    private static NaiveSingleton _instance;

    private NaiveSingleton() { }

    public static NaiveSingleton GetInstance()
    {
        if (_instance == null)
        {
            _instance = new NaiveSingleton();
        }
        return _instance;
    }

    public void someBusinessLogic()
    {
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

First up, notice the sealed keyword, Singletons should always be a "sealed" in order to prevent class inheritance, which could end up with multiple instances and this contrasts the whole point of Singletons.

Also, the constructor must be private to prevent creating new instances.

Now the GetInstance() method, this is the main entry point for the class, it serves as an alternative to the constructor (which we hid using the *private** keyword)* and lets clients access the same instance of this class over and over.
On the first run, it creates a singleton object and assigns it into the static field _instance.
On subsequent runs, it returns the existing object stored in the static field.

To test our code, we could run this snippet on the main() function of our console application:

static void Main(string[] args)
{
    NaiveSingleton s1 = NaiveSingleton.GetInstance();
    NaiveSingleton s2 = NaiveSingleton.GetInstance();

    if (s1 == s2)
    {
        Console.WriteLine("Singleton works, both variables contain the same instance.");
    }
    else
    {
        Console.WriteLine("Singleton failed, variables contain different instances.");
    }
}
Enter fullscreen mode Exit fullscreen mode

And the output should be as expected:

Singleton works, both variables contain the same instance.
Enter fullscreen mode Exit fullscreen mode

Great, both s1 and s2 are essentially the same, right? Ummm, not always.

Of course, there's a culprit, it shouldn't be that straightforward huh? 😁

Why isn't our solution sophisticated enough?

To better explain my point, let me adjust our main() function a little bit and introduce some asynchrony into the problem.

⚠️ Disclaimer: The following part is a little bit advanced (just a little bit, I promise) and needs some basic understanding of the asynchronous operation in C#, if you don't feel comfortable with async, await and C# Tasks, feel free to check my DEV.TO series about the topic where I attempt to make a simple cup of tea, asynchronously.

Back to our main() function:

static void Main(string[] args)
{
    // Delay getting an instance of our singleton by doing so inside a task.
    Task<NaiveSingleton> ts1 = Task.Run(() => NaiveSingleton.GetInstance());
    Task<NaiveSingleton> ts2 = Task.Run(() => NaiveSingleton.GetInstance());

    // Run both tasks in parallel
    await Task.WhenAll(ts1, ts2);

    // Get the result of each task
    NaiveSingleton s1 = await ts1;
    NaiveSingleton s2 = await ts2;

    if (s1 == s2)
    {
        Console.WriteLine("Singleton works, both variables contain the same instance.");
    }
    else
    {
        Console.WriteLine("Singleton failed, variables contain different instances.");
    }
}
Enter fullscreen mode Exit fullscreen mode

The main point of the previous modification is to access the GetInstance() method of the Singleton from two different "contexts" at the same time, for example, two different threads.

📝 Side Note: The previous modification might have not essentially created a whole new thread, but it certainly accessed the GetInstance() method from two different contexts, for an explanation, read through this StackOverflow answer, you can check this shorter one too on the same question.

Now, let's check the output of the previous modification:

Singleton failed, variables contain different instances.
Enter fullscreen mode Exit fullscreen mode

So, what happened?
Check the comments within the snippet for clarifications

public static NaiveSingleton GetInstance()
{
    // Both contexts entered the GetInstance() method at the same time,
    // both of them queried whether _instance is null at the same time,
    // and since _instance was in fact initially null,
    // both succeeded to enter the if clause at the same time 
    if (_instance == null)
    {
         // .. which resulted in the creation of a new Instance 
         // twice for each context
        _instance = new NaiveSingleton();
    }
    return _instance;
}
Enter fullscreen mode Exit fullscreen mode

Let's fix this small bug, and make our Singleton a little bit more Sophisticated.

Sophisticated Singleton

public sealed class SophisticatedSingleton
{ 
    private static SophisticatedSingleton _instance; 
    private static readonly object _lock = new object(); 

    private SophisticatedSingleton() { } 

    public static SophisticatedSingleton GetInstance()
    {
        lock (_lock)
        {
            if (_instance == null)
            {
                _instance = new SophisticatedSingleton();
            }
        }
        return _instance;
    }
}
Enter fullscreen mode Exit fullscreen mode

Straight up, we could notice a couple of lines making this version a little different than its Naïve sibling.

Simply speaking, I've added a lock object and utilized it using C#'s lock() keyword, this will be used to synchronize threads during first access to the Singleton.

During the initial launch of the program, there are no instances yet, multiple contexts (caused by the Task.WhenAll() call) will simultaneously reach the lock at the same time.
The first of them will acquire it and will proceed further creating an instance, while the others will wait for the lock to be released.

Once the first context leaves the lock block, another context that might have been waiting for the lock release will then enter this section.
This time the Singleton field is already initialized and there would be no further need to initialize it once more with a new instance.

Now let's test our fix, I'll replace references to the NaiveSingleton in our Main() method and migrate to the newer SophisticatedSingleton:

static void Main(string[] args)
{
    // Delay getting an instance of our singleton by doing so inside a task.
    Task<SophisticatedSingleton> ts1 = Task.Run(() => SophisticatedSingleton.GetInstance());
    Task<SophisticatedSingleton> ts2 = Task.Run(() => SophisticatedSingleton.GetInstance());

    // Run both tasks in parallel
    await Task.WhenAll(ts1, ts2);

    // Get the result of each task
    SophisticatedSingleton s1 = await ts1;
    SophisticatedSingleton s2 = await ts2;

    if (s1 == s2)
    {
        Console.WriteLine("Singleton works, both variables contain the same instance.");
    }
    else
    {
        Console.WriteLine("Singleton failed, variables contain different instances.");
    }
}
Enter fullscreen mode Exit fullscreen mode

And here goes our expected output:

Singleton works, both variables contain the same instance.
Enter fullscreen mode Exit fullscreen mode

Hurray 🥳!

Eager vs Lazy Initialization

There is yet one another customization to how you'd implement a singleton.

We've been initializing Singletons throughout the previous couple of examples "lazily", which means that an instance would not be initialized unless the getInstance() function has been called and the _instance == null check returned true, only then would an instance be initialized, in other words, _instance would never be initialized unless at least one context needs it.

But what if we want the Singleton's instance to be available right away?

public sealed class EagerSingleton
{
    private EagerSingleton() { }

    private static EagerSingleton _instance = new EagerSingleton();

    public static EagerSingleton GetInstance()
    {
        return _instance;
    }
}
Enter fullscreen mode Exit fullscreen mode

A minor tweak to the original Naïve Singleton could be done, where we initialize the static _instance field inline, making it ready for usage right before needing it.

This has the benefit of making the instance instantly available, but, a major drawback should be accounted for.

Eagerly initializing the field means that it would be initialized even if no one ever needs it, taking into consideration whether the instance itself might require heavy processing or a substantially large amount of memory bytes, this would certainly form an overhead for your application, so be sure to use this technique wisely.

Use Cases of Singletons

Now I can think of a couple of real-life use cases, for example, we could design our own custom Logger class by it being defined as a singleton. No need to create a new instance every time we need to log something into the console or the database.

Speaking of databases, we could also build our database context using the Singleton Pattern, because again, no need for multiple instances, only one is enough.
One would argue that it would be better to register it as a service (depending on the dependency injection framework of your choice) and inject it to whatever the class that needs it, which is totally valid of course, but for simple projects, I think it would be perfectly fine to design the context as a Singleton.

In addition to database connections and logging services, Singleton classes could also be used for driver objects, caching, or thread pools.

Takeaway 📒

So to answer the main question:

What are the similarities and differences between the two approaches?

As you might've already noticed, a Singleton is nothing but some wrapping methods around a core static field packaged into a sealed class that has no public constructors.

A static field indeed helps in making an object globally accessed, but by itself, it neither does help in making it threadsafe nor lazily constructed, it does not introduce encapsulation into the game, meaning you'll access the field directly without a custom "getter" (like getInstance() method in our case) and it might be dangerous to expose the field like so in some cases.

Here are some key differences that could also be included in our research:

  1. A Singleton can implement interfaces and inherit from other classes. While a static class cannot inherit their instance members. So Singleton is more flexible than static classes and can maintain state.
  2. A Singleton can be initialized lazily, while a static class is generally initialized when it is first loaded.
  3. Singleton Objects are stored on the heap while static classes are stored in stack.
  4. Singleton Objects can dispose but static classes can't.
  5. Singleton Objects can clone but static classes can't.

To sum up 📒, I could use this answer from Stackoverflow:

Static classes are not for anything that needs state. It is useful for putting a bunch of functions together i.e Math (or Utils in projects). So the class name just gives us a clue where we can find the functions and nothing more.

So whenever you need to choose between the two approaches, ask yourself the following question:

If there is a bunch of functions that should be packaged together: go for static.
Anything else which needs single access to some resources and state maintaining, singleton should be the way to go.

Side Note 🗒️:
Singletons are sometimes considered as anti-patterns, here's why:

As we've discussed, Singletons are basically a way to use global variables, global variables are bad because any code anywhere in the system can change their values. So when debugging, it can be challenging to figure out which code path leads to the Singleton's current state.
The previous problem can be handled by proper encapsulation mechanisms though, just be a little bit mindful about how you implement them.

I'd recommend reading this StackOverflow thread about the topic.

Outro

A Singleton class is a pattern, one of many in fact. It would be a great idea to read more about other design patterns too, Refactoring Guru hosts explanations to most of them in a nicely illustrated way, be sure to check them out.

It was my pleasure to conduct this research, and I hope you'd find it interesting and informative. In case you have something on your mind, let's discuss it in the comments section.

Keep Coding 💻!
See you soon. 👋

Top comments (0)