Early Access: 87 spots left.

Claim

Course content

No content available

The Singleton pattern says: this resource exists exactly once for the whole program, and there is one well-known place to ask for it. The class enforces this itself by closing the door on direct allocation and providing a single accessor that is the only way in. Two callers that ask for the resource get the same object. Any state the resource holds is, by construction, shared between them.

The two mechanics are simple. The constructor is made inaccessible, so no other code can create a second instance. An accessor (getInstance by convention) returns the shared instance, creating it once if necessary. That is the whole pattern. The rest of this page builds it around a logger in all three languages, then deals with the question interviewers always reach for: what happens under threads.

Singleton is the right answer when there genuinely should only be one of something for the lifetime of the process, and global access to that one thing is itself part of the design. Examples that fit cleanly: a process-wide logger that all subsystems write to, a configuration registry loaded at startup, a connection pool that owns the precious set of database connections, the JVM clock.

The defining feature in each case is that having two of the resource would be wrong, not just wasteful. Two loggers would lose messages between them. Two configuration registries could disagree. Two connection pools could exhaust the database. The Singleton mechanic exists to make a second instance impossible.

Every part of the codebase that wants to log constructs its own Logger and ends up with its own private message list. "The" logger is actually many loggers, each silently dropping the messages the others see. There is no way to retrieve a unified history because none of the instances has it.

A common workaround is to wrap the public constructor with a static method that always returns a fresh instance, like public static Logger getInstance() { return new Logger(); }. The shape now looks like the Singleton pattern, but the cache step is missing, so the behaviour is unchanged.

What this costs in production

Logging worked in tests but the production log file was always half empty. Each component had called new Logger() and was writing to its own private list, so 'the' log was actually a dozen disconnected logs. During an incident the on-call engineer pulled the logger they had a handle on and saw none of the messages the failing component had written, because that component held a different instance entirely.

A singleton opportunity usually announces itself as accidental multiplicity: several instances of a thing the design assumed was unique. The symptoms look like these.

SymptomWhat it looks likeWhy Singleton might be the answer
Multiple instances of what should be a single resourceTwo loggers in different modules silently dropping each other's messagesThere is only one logger conceptually; the type system should enforce that
A global static map holding shared statestatic Map<String, Object> APP_CONFIG = new HashMap<>();The resource already behaves like a Singleton, just without the discipline
A class that callers always construct with the same argumentsnew ConnectionPool("postgres://...") repeated everywhereThere is one canonical instance; centralise its construction
Tests that pass when run alone but fail in suitesOrder-dependent leaks of mutable state across test filesOften a sign that something Singleton-like exists informally and should be made explicit

The door is closed on second instances. In Java the constructor is private, so no other class can allocate a Logger; the one instance lives in a private static final field and getInstance returns it. In C++ the constructor is private and the copy operations are deleted, so nobody can clone their way around the accessor; the instance itself hides inside getInstance as a function-local static (this form has a name, the Meyers singleton). Python has no private constructors, so the module boundary carries the discipline instead: the module creates one instance at import time, importers call get_logger(), and the underscore on _instance marks it as off-limits by convention.

All callers now reach the same object, so any state the logger holds is genuinely global. Two components that log through getInstance() write to the same list, and the incident-day problem from the callout disappears: there is exactly one history, and everyone is looking at it.

Each language leans on a different mechanism for the create-once step, and the choice is not cosmetic. Those three mechanisms are also what make the pattern thread-safe, which is the next section.

Almost every singleton interview turns into a thread-safety question, because the naive lazy version is one of the best-known concurrency bugs in object-oriented code. if (instance == null) instance = new Logger(); reads correctly and works on one thread. Under two threads it races: both can evaluate the null check before either assignment happens, both allocate, and the second assignment overwrites the first. Any caller that grabbed the first instance now holds state the rest of the program will never see again. The bug is intermittent, load-dependent, and invisible in single-threaded tests, which is exactly why interviewers like it.

Each language has a canonical way out, and the guarantees are worth knowing by name. In Java, the eager static final field from the refactor is safe because the JVM guarantees a class's static initialisers run exactly once, even when many threads trigger class loading at the same moment. When laziness genuinely matters (the instance is expensive and might never be used), the holder idiom keeps the same guarantee while deferring construction: the instance lives in a private nested class that the JVM does not initialise until getInstance first touches it. Double-checked locking with a volatile field also works and is still asked about; without volatile it is broken, because another thread can observe a half-constructed object through the data race.

In Python, the module-level instance from the refactor is the language's own singleton mechanism: the import system runs a module's top level exactly once and holds an import lock while doing it. Note that the GIL does not rescue the lazy classmethod version, because the scheduler can switch threads between the None check and the assignment; if you need lazy construction inside a class, take a real threading.Lock. In C++, the function-local static has been guaranteed since C++11: concurrent first calls block until one thread finishes constructing, and every later call just returns the reference. std::call_once with a std::once_flag is the explicit alternative when construction needs arguments or can fail.

Here is the broken version and the fixes, language by language.

getInstance()getInstance()ServiceAServiceBLogger
Every caller goes through getInstance(), which returns the one cached Logger. A private constructor blocks new Logger(), so all callers share a single message list.

The JDK's Runtime.getRuntime() is the canonical singleton in Java: one process, one runtime, one well-known accessor. Desktop.getDesktop() follows the same shape. Just as often you meet the discipline one level up: SLF4J's LoggerFactory and java.util.logging.LogManager hold registries of named loggers, so asking for the same name twice returns the same instance. That is the singleton guarantee applied per key rather than per process.

Spring complicates the vocabulary in a useful way. The default bean scope is called singleton, but it means one instance per container, enforced by the framework rather than by a private constructor. The class itself stays a plain, testable object, and the container guarantees the one-ness. That design gets you the sharing without the global access point, and it is the main reason modern Java code hand-writes far fewer singletons than it used to.

Python's runtime is full of singletons enforced by identity rather than class machinery. None, True, and False are each one object per interpreter, which is why comparing with is works for them. Every imported module is itself a singleton: import config in ten files gives all ten the same module object, which is exactly why the module-level instance is the idiomatic implementation. logging.getLogger("app") mirrors the Java logger registries: same name, same object, every time.

C++ programs meet the pattern through the standard streams: std::cout, std::cerr, and std::cin are single global objects constructed by the runtime before main runs. Hand-written C++ singletons are overwhelmingly the Meyers form; static Foo& instance() accessors show up across large codebases from compilers to game engines.

ProsCons
The type system enforces one instance; a second cannot be constructed by accidentGlobal mutable state with a friendly name; every user is invisibly coupled to it
One well-known access point; no need to thread the object through ten constructorsDependencies are hidden: a class's constructor signature no longer admits what it uses
Eager and lazy variants both have thread-safe canonical forms in every mainstream languageTests that touch the singleton share state; suites become order-dependent without a reset hook
Lifecycle is trivial: created once, lives as long as the processHard to substitute a fake or a variant; testability usually argues for injection instead
Universally recognised; reviewers read the intent instantlyFrequently over-applied to things that are merely convenient to reach globally, not genuinely unique

Singleton is sometimes called an anti-pattern, and the criticism is real. A Singleton is global mutable state with a friendly name. Callers do not declare their dependency on it, so the coupling between the caller and the Singleton is invisible at the type level. Tests cannot inject a fake without resorting to bytecode manipulation or static replacement. Different parts of the program can mutate the shared state and leak that mutation across test boundaries.

For most use cases that look like "shared service" (a repository, a notifier, a clock used by tests), the better tool is constructor injection. Pass the dependency in. The DIP exercise on this site teaches that move directly. Reach for Singleton only when the resource is genuinely process-wide, when global access is part of the design intent, and when no caller is realistically going to want a different instance for testing or extension. Loggers and configuration registries pass that test. Most other things do not.

Three flavours of question recur. The first is implementation: "write a thread-safe singleton". The expected moves in Java are the eager static final field, the holder idiom when laziness is required, and double-checked locking named together with its volatile requirement. Effective Java's one-element enum is worth mentioning as the bulletproof variant, because it is immune to the reflection and serialisation attacks that can mint a second instance past a private constructor. In Python the expected answer is the module-level instance, with the lock-based classmethod as the in-class alternative; in C++ it is the Meyers singleton, with std::call_once when construction takes arguments.

The second is the contrast: "why not just make everything static?". A static utility class fits when there is no state and no polymorphism, pure functions in the style of Math. Singleton earns its place when there is state to own (the message list, the pool of connections) or an interface to implement, because an instance can implement interfaces, be handed around as an argument, and be replaced by a subclass or a fake at a seam. Static methods can be none of those things. If the answer to "would anyone ever want to swap this out?" is yes, static is the wrong tool.

The third is the critique: "is Singleton an anti-pattern?". The strong answer names the costs (hidden coupling, shared mutable state, test pollution), names the alternative (constructor injection, with a container or the composition root holding the one instance), and still defends the honest uses: a process-wide logger or configuration registry that would be wrong duplicated. Interviewers are listening for judgement, not a verdict.

The companion exercise gives you a Logger class with a public constructor and a broken getInstance that returns a fresh instance every call. Refactor it into a real Singleton. The validator confirms that two getInstance calls return the same reference, that the constructor is no longer public, and that state logged through one reference is visible through another.

Back to Low Level Design