Skip to content

Singleton

Intent

Guarantee that a class has exactly one instance and give every caller a known way to reach it. Two problems hide in that sentence: a resource that is genuinely singular (one registry of loggers, one device handle, one settings object) and a construction that must happen at most once even when several threads ask at the same moment.

When to use and when not to

Use it when

  • Two instances would be a bug, not a waste: a registry keyed by name, a process-wide metrics sink, the handle to one piece of hardware.
  • Callers are many and scattered with no composition root to thread an object through; library code such as logging lives here.
  • You are wrapping a framework that already works this way and want one access path rather than two.

Leave it out when

  • "There is one of them today." One parking lot, one elevator controller, one scheduler: that is one instance by convention, built in main and injected, so a second lot is a second object and a test builds its own.
  • The object holds mutable state that tests need fresh. The reset hook appears, then the test-ordering bug.
  • What you want is global access, not single existence. That is a global variable wearing a class; pass the object instead.
  • You are counting on "one" across processes. A Singleton is per interpreter: a server with eight worker processes has eight of them.

Structure

The classic form keeps its instance in a class attribute; the metaclass form moves that logic out of the class; the injected form does not enforce anything.

classDiagram
    class ConfigManager {
        -_instance: ConfigManager
        -_lock: Lock
        -_guard: RLock
        -_settings: Dict~str,str~
        +__new__(cls) ConfigManager
        +get(key: str) str
        +set(key: str, value: str) None
        +load(values: Mapping~str,str~) None
        +snapshot() Dict~str,str~
        +reset() None
    }
    class SingletonMeta {
        <<metaclass>>
        -_instance: Any
        -_lock: Lock
        +__call__(args, kwargs) Any
    }
    class FeatureFlags {
        +flags: Dict~str,bool~
        +enabled(name: str) bool
    }
    class Borg {
        -_shared_state: Dict~str,Any~
    }
    class AppConfig {
        +region: str
        +max_retries: int
    }
    class Uploader {
        -_config: AppConfig
        +describe() str
    }
    SingletonMeta ..> FeatureFlags : instantiates once
    Uploader o-- AppConfig : injected

_instance and _lock are class-level in both forms. ConfigManager does the work in __new__; SingletonMeta intercepts the call before __new__ or __init__ run, so FeatureFlags is an ordinary class. Borg shares state, not identity. Uploader is the contrast: it takes an AppConfig and never asks whether another exists.

Canonical example in Python

The classic form first (code/patterns/singleton.py, tested by code/patterns/tests/test_singleton.py):

code/patterns/singleton.py — __new__ with double-checked locking
class ConfigManager:
    """The classic form: ``__new__`` returns the one instance, guarded by a lock.

    Two locks, two jobs: ``_lock`` (class level) protects ``_instance`` during
    creation; ``_guard`` (instance level) protects ``_settings`` afterwards. There
    is deliberately no ``__init__``: Python runs ``__init__`` on *every*
    ``ConfigManager()`` call, so state set up there would be wiped each time the
    access point is used. ``_init_once`` runs under the lock, exactly once.
    """

    _instance: ClassVar[ConfigManager | None] = None
    _lock: ClassVar[threading.Lock] = threading.Lock()

    def __new__(cls) -> ConfigManager:
        if cls._instance is None:  # fast path: no lock once the instance exists
            with cls._lock:
                if cls._instance is None:  # second check: the loser of the race stops here
                    instance = super().__new__(cls)
                    instance._init_once()
                    cls._instance = instance  # published last, so readers see a finished object
        return cls._instance

    def _init_once(self) -> None:
        self._guard = threading.RLock()
        self._settings: dict[str, str] = {}

    def get(self, key: str) -> str:
        with self._guard:
            try:
                return self._settings[key]
            except KeyError:
                raise NotFoundError(f"no setting named {key!r}") from None

    def set(self, key: str, value: str) -> None:
        with self._guard:
            self._settings[key] = value

    def load(self, values: Mapping[str, str]) -> None:
        with self._guard:
            self._settings.update(values)

    def snapshot(self) -> dict[str, str]:
        with self._guard:
            return dict(self._settings)

    @classmethod
    def reset(cls) -> None:
        """Test hook: forget the instance. Needing this is the first sign injection would be simpler."""
        with cls._lock:
            cls._instance = None

Three decisions to say out loud:

  • __new__, never __init__. Python calls __init__ on every ConfigManager() call, including the ones where __new__ handed back the existing object, so state set up there would be wiped on each access. _init_once runs under the lock, exactly once, and the class has no __init__ at all.
  • Check, lock, check again, publish last. The first check skips the lock on every call after the first; the second stops the thread that lost the race; assigning cls._instance after _init_once means no reader sees a half-built object. The lock itself is cheap (an uncontended mutex is about 17 ns on the latency ladder: 100k accesses a second spend 100,000 x 17 ns = 1.7 ms in it), so the fast path is about contention during a burst of first calls, not speed.
  • Two locks, two jobs. _lock (class level) protects creation; _guard (instance level, re-entrant) protects _settings. Say which lock covers what: set cannot race with __new__ because it needs an instance that only a finished __new__ returns.

reset exists because the tests needed it. That is the tell: a Singleton meeting a test suite grows a back door, and every test becomes order-dependent.

Running python -m patterns.singleton prints:

--- classic: every ConfigManager() call returns the same object ---
first is second: True; second.get('region') -> eu-west-1
--- 16 threads race to create the first instance ---
distinct instances: 1
--- metaclass: __init__ runs once, later arguments are ignored ---
flags is again: True; beta enabled: True
--- cached factory and Borg ---
default_config() is default_config(): True
left is right: False; right.theme: dark
--- injection: one instance by convention, built in main ---
upload to eu-west-1 with 5 retries
a test builds its own: upload to local with 0 retries

Pythonic variant

The shortest Singleton in Python is a module. import runs it once and caches it in sys.modules, so a module-level object is created once and shared by every importer, with no lock and no __new__:

# settings.py: the module is the singleton; import caches it in sys.modules
from patterns.singleton import AppConfig

config = AppConfig(region="eu-west-1")

# anywhere else
from settings import config

Three more forms, each fixing one weakness of the classic:

code/patterns/singleton.py — metaclass, cached factory, Borg
class SingletonMeta(type):
    """Metaclass form: ``Cls()`` is intercepted before ``__new__`` and ``__init__`` run.

    ``__init__`` therefore runs exactly once, and every class built with this
    metaclass gets its own ``_instance`` and ``_lock``: a subclass is a second
    singleton, not a second copy of the parent.
    """

    def __init__(cls, name: str, bases: tuple[type, ...], namespace: dict[str, Any]) -> None:
        super().__init__(name, bases, namespace)
        cls._instance = None
        cls._lock = threading.Lock()

    def __call__(cls, *args: Any, **kwargs: Any) -> Any:
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__call__(*args, **kwargs)
        return cls._instance


class FeatureFlags(metaclass=SingletonMeta):
    """Constructor arguments after the first call are silently ignored: a known trap."""

    def __init__(self, flags: Mapping[str, bool] | None = None) -> None:
        self.flags = dict(flags or {})

    def enabled(self, name: str) -> bool:
        return self.flags.get(name, False)


@dataclass(frozen=True, slots=True)
class AppConfig:
    """A frozen value: safe to share, impossible to corrupt, trivial to build twice in tests."""

    region: str = "us-east-1"
    max_retries: int = 3


@cache
def default_config() -> AppConfig:
    """The function is the access point and ``cache`` is the instance store.

    ``cache`` holds no lock around the call: two threads racing on the very first
    call can each build an ``AppConfig`` and the cache keeps the last one. Harmless
    for a frozen value, wrong for anything that owns a connection or a thread.
    """
    return AppConfig()


class Borg:
    """Monostate: many instances, one shared ``__dict__``. Identity differs, state agrees."""

    _shared_state: ClassVar[dict[str, Any]] = {}

    def __init__(self) -> None:
        self.__dict__ = self._shared_state
  • Metaclass. SingletonMeta.__call__ runs before __new__ and __init__, so __init__ executes once and the class body stays clean. Each class gets its own _instance: a subclass is a second singleton, where the __new__ form would hand a subclass its parent's object. The trap: later constructor arguments are silently ignored.
  • @cache on a factory. The function is the access point and the cache is the store. No lock guards the first call, which is harmless for a frozen AppConfig and wrong for anything owning a connection or a thread.
  • Borg. Many instances, one __dict__: identity differs, state agrees. Know it; it is global mutable state with extra steps.

The answer this handbook prefers enforces nothing:

code/patterns/singleton.py — one instance by convention
class Uploader:
    """Takes its configuration as a constructor argument.

    Nothing here enforces that only one ``AppConfig`` exists: ``main`` builds one
    and passes it along, and a test builds a different one. One instance by
    convention rather than by construction is this handbook's default answer.
    """

    def __init__(self, config: AppConfig) -> None:
        self._config = config

    def describe(self) -> str:
        return f"upload to {self._config.region} with {self._config.max_retries} retries"

main builds one AppConfig and passes it to Uploader; a test builds a different one. Nothing enforces uniqueness and nothing needs to: the shared object was the point, never the guarantee.

Reach for When
A module-level instance Immutable configuration or a stateless helper
Constructor injection Mutable state, anything a test wants fresh; the default
__new__ with a lock or a metaclass Construction at most once under concurrency, with no composition root
@cache on a zero-argument factory A lazily built immutable value

Real-world usage

  • logging.getLogger(name) is the pattern done well: one Manager maps names to loggers and creates them under logging._lock, so the same name always yields the same logger. The registry is singular; the loggers are many.
  • sys.modules makes every module a singleton, which is why Django's django.conf.settings is a module-level LazySettings object that configures itself on first attribute access.
  • Interned values. None, True, False, Ellipsis and the empty tuple are singletons; x is None is correct only because of that guarantee.
  • functools.cache on a factory is the stdlib's one-line memoised constructor, as in default_config above.
Looks like Singleton How to tell them apart
Dependency Injection The alternative, not a relative: every collaborator gets the same object without any class enforcing it, and the enforcement is what makes Singletons hard to test.
Module Python's native Singleton: one object per import, no lock needed because the import system serialises module execution.
Borg (Monostate) Same state, different identities; a is b is false, a.x == b.x is true.
Flyweight Many objects sharing intrinsic state, not one object; interned strings and Enum members are flyweights.
Object Pool One becomes N: a bounded set of reusable instances with acquire and release. The pool is often the only Singleton a system needs.
Factory Method A factory decides which class to build; a Singleton access point decides whether to build at all. getLogger is both.

Where it appears in LLD problems

Interview tips

Interview tip

Give the three-sentence answer before you are asked: "ConfigManager is a Singleton, __new__ with double-checked locking and no __init__; in Python I would write it as a module-level instance; here I would build one in main and inject it, because the tests want their own." Naming the thread-safe form proves you can write it; choosing not to proves judgement.

Common mistake

Putting state in __init__. It runs on every call, so the second ConfigManager() silently resets the first one's settings and the bug shows up as "config sometimes empty" far from the cause. Runner-up: believing the guarantee survives fork; one instance per process means eight instances under eight workers.