Design Patterns: Creational · Creational Patterns

Singleton Pattern

Guaranteeing exactly one instance of a class exists application-wide.

  • 7 min read
  • 8 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The pattern
  3. Four more ways to write it
  4. Choosing among the creational patterns
  5. Common mistakes
  6. Check yourself
  7. Key takeaways
  8. Quick revise
  9. Practice

Description

Singleton is the creational pattern for an object there must be exactly one of in the whole application - and a way for everyone to reach that one object. It's the simplest design pattern to write and the easiest to overuse, because "exactly one, reachable from anywhere" is also a description of a global variable. This skill covers when one instance is genuinely required, the ways to build it safely (lazily or eagerly, with threads), and what it costs.

Some objects stand for something the whole application must share: the loaded configuration, a pool of database connections, the client that holds a streaming connection to a feature-flag server. If two parts of the code each call new ConfigManager(), you get two copies that can drift apart - one reloaded, one stale - and nothing reports it. With a connection pool it is worse: two pools each believe they own the database's connection limit.

A public constructor can't prevent this - anyone can call it, as often as they like. Singleton takes the constructor away from callers and hands out one shared instance instead.

We'll go from simple to more technical:

  1. The pattern in Java and Python.
  2. Four more ways to write it, and when each one builds the instance.
  3. Thread safety: what the JVM and Python's import system guarantee, and what double-checked locking needs.
  4. Choosing among the creational patterns, and the mistakes to avoid.

The pattern

Java

class ConfigManager {
    private static ConfigManager instance;
    private ConfigManager() { /* load config */ }
    public static synchronized ConfigManager getInstance() {
        if (instance == null) instance = new ConfigManager();
        return instance;
    }
}

Python

import threading


class ConfigManager:
    _instance: "ConfigManager | None" = None
    _lock = threading.Lock()

    @classmethod
    def get_instance(cls) -> "ConfigManager":
        with cls._lock:
            if cls._instance is None:
                cls._instance = cls()   # load config
            return cls._instance

The private constructor blocks new ConfigManager() from outside the class entirely - getInstance() is the only way in, and it always hands back the same object.

Python has no private constructors - nothing stops a call to ConfigManager() - so a Python singleton is usually just a module-level instance: config = ConfigManager() in config.py. A module runs once per process and is cached in sys.modules, so every from config import config gets the same object.

Four more ways to write it

The synchronized method above is correct, but it takes a lock on every call. These are the usual alternatives:

Java

// 1. Eager: built when the class is initialized. Thread-safe for free.
class Eager {
    private static final Eager INSTANCE = new Eager();
    private Eager() {}
    static Eager getInstance() { return INSTANCE; }
}

// 2. Holder idiom: lazy AND lock-free. Holder is initialized only when getInstance() first touches it.
class Lazy {
    private Lazy() {}
    private static class Holder { static final Lazy INSTANCE = new Lazy(); }
    static Lazy getInstance() { return Holder.INSTANCE; }
}

// 3. Double-checked locking: lazy, and locks only until the instance exists.
class Checked {
    private static volatile Checked instance;              // volatile is required
    private Checked() {}
    static Checked getInstance() {
        Checked local = instance;
        if (local == null) {                                // fast path: no lock once built
            synchronized (Checked.class) {
                local = instance;
                if (local == null) instance = local = new Checked();   // second check, inside the lock
            }
        }
        return local;
    }
}

// 4. Enum: the JVM creates INSTANCE once and won't make more by reflection or deserialization.
enum Registry { INSTANCE; }

Python

# 1. Module-level instance: built when the module is first imported. The usual Python singleton.
# config.py
settings = Settings()
# elsewhere: from config import settings


# 2. Lazy, cached function: built on the first call, then cached.
import functools


@functools.cache
def get_settings() -> Settings:
    return Settings()


# 3. Double-checked locking: lazy, and locks only until the instance exists.
import threading


class Checked:
    _instance = None
    _lock = threading.Lock()

    @classmethod
    def get_instance(cls):
        if cls._instance is None:                  # fast path: no lock once built
            with cls._lock:
                if cls._instance is None:          # second check, inside the lock
                    cls._instance = cls()
        return cls._instance


# 4. __new__: every Registry() call returns the same object - but __init__ runs every time.
class Registry:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

When is "eager" actually built? Not when the class is loaded, but when it is initialized - on its first active use. If getInstance() is the only static member anyone touches, eager and lazy behave almost the same. The difference appears when the class has other static methods or fields: calling any of them builds the instance too. The holder idiom avoids that, because its nested class is initialized separately.

The eager form and the holder idiom get their thread safety from the JVM: a class's static initialization runs exactly once, and other threads wait until it finishes. Double-checked locking coordinates by itself, and both of its safety parts are required. Without the second check, two threads that both passed the first check each construct an instance. Without volatile, another thread can see the reference before the object's fields are written.

In Python, a module-level instance gets the same guarantee from the import system: a module's code runs once, under an import lock, and other threads importing it wait until it finishes. @functools.cache gives no such guarantee - two threads that call it at the same moment can both build an instance. Double-checked locking still needs both checks, but no volatile: the object is fully built before cls._instance = ... runs.

Choosing among the creational patterns

If you need...Reach forBecause
Exactly one shared instance, by designSingleton (this lesson)Two copies would be a bug (e.g. two connection pools fighting over the same database connections).
One place that decides which concrete class to buildFactory MethodCallers depend on an interface; adding a type changes the factory, not every caller.
A matched family of related objects that must vary togetherAbstract FactorySwapping the factory swaps the whole family, so mismatched pieces can't be combined.
An object with many optional parts, assembled step by stepBuilderNamed, optional steps instead of long positional constructors; validate once in build().
Many near-identical objects that are expensive to set upPrototypeCopy a ready-made template instead of repeating costly construction.

Common mistakes

MistakeWhat happensDo this instead
A Singleton because "there's only one right now"a hidden global that's hard to replace in testscreate one instance at startup and pass it in
Calling getInstance() inside business methodsthe dependency is invisible and can't be fakedinject the shared instance through constructors
Lazy initialization with no locktwo threads can each build an instancethe holder idiom, an enum, a module-level instance, or a lock
Double-checked locking without volatile (Java)other threads can see a half-built objectdeclare the field volatile
Keeping mutable state in a singletontests leak state into each other; the state isn't shared across processeskeep it stateless, or move the state to a database or cache

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. A class has private static final Cache INSTANCE = new Cache(); and a static Cache.version() method. A program only ever calls Cache.version(). Is the cache built?

Show answer

Yes. Calling any static method initializes the class, and initialization runs every static field initializer - including new Cache(). The holder idiom avoids this by keeping the instance in a nested class that only getInstance() touches.

2. Double-checked locking without volatile - what can go wrong?

Show answer

Another thread can see a non-null instance before the constructor's writes are visible to it, and use a half-built object. volatile forbids that reordering.

3. OrderService calls PaymentGateway.getInstance() inside its methods. Why is OrderService hard to unit test?

Show answer

The dependency is hidden and global, so a test can't pass in a fake gateway. Passing the gateway through OrderService's constructor fixes that - and the application can still create exactly one gateway and hand it to everyone.

Key takeaways

  • Use it only when a second instance would be wrong, not merely unnecessary.
  • Prefer an enum (or an eagerly created static final field) unless you truly need lazy creation - in Python, a module-level instance.
  • Lazy + multi-threaded means the holder idiom, a synchronized method, or double-checked locking with a volatile field - in Python, a threading.Lock around the check, or double-checked.
  • It behaves like a global variable - that is the price you pay in testability.
  • "Exactly one" only holds inside a single JVM (or Python process - each worker of a multi-process server has its own); once you scale out, the shared state must move to a database or cache.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You needJavaPython
One instance, built at start-upstatic final field or enummodule-level instance
Lazy, lock-freeholder idiommodule-level instance (import is lazy)
Lazy with a locksynchronized or double-checked + volatilethreading.Lock, double-checked
Resistant to reflection and serializationsingle-element enum__copy__, __reduce__ returning the instance
One shared instance for application servicesone instance built at startup and injectedthe same

Snippets worth knowing by heart

Java

enum Registry { INSTANCE; }

final class Lazy {
    private Lazy() {}
    private static class Holder { static final Lazy INSTANCE = new Lazy(); }
    static Lazy getInstance() { return Holder.INSTANCE; }
}

Python

# config.py
settings = Settings()

# anywhere else
from config import settings

The traps

  • "Only one right now" - not a reason; inject one instance instead.
  • getInstance() inside business methods - hidden, unfakeable dependency.
  • Unlocked lazy init - two threads, two instances.
  • DCL without volatile - half-built objects.
  • Mutable state - leaks across tests and isn't shared across processes.

Staff-level points, one line each

  • Second instances: reflection on the private constructor; deserialization without readResolve; copy/pickle without __copy__/__reduce__.
  • The enum singleton refuses reflection ("Cannot reflectively create enum objects") and deserializes by name.
  • Classloaders (Java) and duplicate module names (Python) each make their own "single" instance.
  • A __new__ singleton re-runs __init__ on every call - and can wipe its own state.
  • Holder and eager forms rely on the once-only class-init lock; beware initializers that depend on each other across threads.
  • @functools.cache isn't once-only: four concurrent first calls built four instances.
  • Singleton state leaks between tests - give each test its own instance via injection.
  • "Exactly one" is per process: N workers or pods means N instances; shared state belongs in shared storage.
  • Container singletons (Spring's default scope) give one instance without global access.

Say it in an interview

  • "Two connection pools would fight over the database's connection limit, so this genuinely must be one."
  • "I'd create it once at startup and pass it in, rather than call getInstance() everywhere."
  • "An enum singleton survives reflection and serialization."
  • "Once we run more than one pod, that counter has to live in Redis, not in a singleton."

Practice

This skill comes with 8 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Singleton Pattern free

Implement a Thread-Safe Singleton Class

HardProCoding problemJava · Python

The lazy Singleton from the previous exercise works fine one thread at a time, but if two threads both see instance == null before either has finished assigning it, both can end up constructing their own Solution - breaking the "exactly one instance" guarantee.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Singleton Pattern free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime