StudyDSA logoStudyDSA

Command Palette

Search for a command to run...

Sign InSign Up
Sign Up

Data StructuresAlgorithmsBig-O NotationDesign PatternsSystem DesignMachine LearningPhysicsRoboticsAI Research

Factory MethodAbstract FactoryBuilderPrototypeSingletonAdapterBridgeCompositeDecoratorFacadeFlyweightProxyChain of ResponsibilityCommandIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

Definition
StudyDSA

Where complexity meets clarity.
By Armas Zarra.

Topics

  • Data Structures
  • Algorithms
  • Big-O Notation
  • Robotics
  • AI Research
  • Machine Learning

Practice

  • Blind 75
  • LeetCode 75
  • NeetCode 150

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Armas Films LLC

Design PatternsSingleton

Singleton

A creational pattern that guarantees a class has exactly one instance and gives the whole program a single access point to it, for better and for worse.

Definition

Ask for the most famous design pattern and you'll hear this one named, usually with an argument attached. Singleton is the Gang of Four creational pattern that guarantees a class has exactly one instance and hands the whole program a single point of access to it. The official intent says both halves out loud: ensure a class has only one instance and provide a global point of access to it. One class plays every role, with a locked constructor, a private static field holding the lone instance, and a public accessor everyone calls.


Strip the ceremony and a singleton is really just a global variable with a bouncer at the constructor. The bouncer half is genuinely useful when a second instance would be a correctness bug, like two connection pools each believing they own the database's connection limit. The global half is the part modern codebases regret, and it earned the pattern a published case against it. This page treats the two halves separately, because the verdict on each is different.

One Instance, Enforced

Here's the textbook shape: a Logger whose constructor is off-limits, whose only instance lives in a private static field, and whose get_instance() accessor creates lazily on first call. The example below grabs the logger twice under two different names and writes one line through each. Before reading past the code, predict the two printed values:

It prints True and 2. The names a and b are two references to one object, so both log lines landed in the same list, which is exactly the behavior a logger wants: one paper trail, no matter who writes. The arrangement works like a building with one shared printer. Everyone's jobs come out of the same machine, which is the feature, and everyone's jobs interleave in the same tray, which is the bug. The analogy understates one thing: the office printer is at least visible in the hallway, while a singleton gets used inside methods whose signatures never admit it exists.

The Race, and the Famous Broken Fix

The lazy accessor above carries a comment warning that it isn't thread-safe, and the warning has real history behind it. Two threads can both evaluate instance == null as true and both run the constructor, producing two instances of a class whose entire job was preventing that. The intuitive repair, checking null, locking, then checking null again before constructing, looked airtight enough that it spread through a generation of Java books as double-checked locking. Then the Double-Checked Locking is Broken declaration, signed by Bill Pugh, Doug Lea, Joshua Bloch, and other JVM memory-model experts, showed the compiler and CPU may reorder writes so that a thread sees a non-null reference to an object whose fields aren't initialized yet. Reading half-built objects through a correctly-typed reference is as nasty as concurrency bugs get, and the idiom only became fixable in Java 5 with volatile.


The practical lesson survived the history: don't hand-roll lazy initialization under concurrency, because every mainstream platform now ships a primitive that does it correctly. That's the second code block's job.

What You'd Write Today

The classic class-with-accessor shape is mostly a Java-era artifact, and each language has since grown a shorter, safer spelling. Python's is barely visible: modules are imported once and cached, so a module-level object is already a lazy singleton. Go ships sync.Once to make the one-time init race-proof, Rust's LazyLock puts a thread-safe lazy value in a plain static, and C++11 made the function-local static initialize exactly once by language guarantee. Same pattern, eight dialects:

Two of these deserve a second look. The Java tab's holder idiom gets thread safety from the class loader itself, with no locks anywhere in sight, by exploiting the JVM's guarantee that a class initializes exactly once on first use. And the Rust tab from the previous block is the strictest take on this page: the naive racy version simply doesn't compile in Rust, because a mutable global is unsafe by definition and OnceLock is the checked door the compiler points you through.

The Case Against It

Singleton is the only pattern in the catalog whose main body of literature argues against using it. The sharpest version comes from Miško Hevery, who ran Google's testing-culture group and titled the argument Singletons are Pathological Liars: singletons are global state, and global state lets objects "secretly get hold of things which are not declared in their APIs". A method signature that reads charge(amount) while internally reaching for a payment gateway singleton is lying about what it needs, and every such lie compounds: you can't know what any call actually touches without reading every body beneath it.


Testing is where the bill arrives. A test for that charge() method can't substitute a fake gateway, because the dependency is fetched through a global accessor the test doesn't control, and state left in the singleton by one test leaks into the next. Order-dependent test suites are the classic symptom. None of this indicts having one instance. The indictment is fetching it through a global access point, which is why the cure is usually dependency injection: construct the single instance once at startup, then pass it openly to everything that needs it. The signatures go back to telling the truth, and the instance count never changed.

Singleton: Step-by-Step

When the pattern genuinely fits, building it is four moves:

1. Lock the Constructor

A private constructor makes new Logger() a compile error everywhere outside the class. Without this lock the rest is decoration, since any caller could mint instance number two on a whim.

2. Hold the Only Instance Privately

A private static field stores the lone object, starting empty for lazy creation. The class becomes its own warehouse: the only Logger that will ever exist lives inside the Logger class definition itself.

3. Serve Everything Through One Accessor

get_instance() builds the object on first call and returns the stored one ever after, so every caller in the program converges on the same reference. This accessor is also the global access point the critics aim at.

4. Get Thread Safety From the Platform

The naive null check races under threads, and the hand-rolled fix has a famous broken history. Every mainstream language now ships a checked primitive for exactly this, from Java's holder idiom to Go's sync.Once to Rust's LazyLock.
Singleton, Static Class, or Dependency Injection

You might think a class with only static methods is a singleton by another name. It's missing the property that makes the pattern worth anything: a static class isn't an object, so it can't implement an interface, can't be passed as an argument, and can't be swapped for a subclass or a fake. A singleton is a real instance that happens to be unique, which keeps polymorphism on the table. If you're choosing between the two, that interface implementing ability is the entire decision.


The other comparison is dependency injection, and it's less a confusion than a successor. A DI container also constructs exactly one instance of a shared service, so the "singleton" half survives intact. What DI deletes is the global accessor: dependencies arrive through constructors, visible in every signature, swappable in every test. When someone says singletons are fine because their framework manages singleton-scoped beans, both sides of that argument are right, because the scope kept the name while the global access point, the part Hevery called the lie, is gone.

Best Practices

The pattern is three lines of mechanism and twenty years of operational lessons:


Most code that reaches for this pattern needs a single instance, not a global access point, and those are different requirements. A connection pool created once at startup and passed to whoever needs it is still singular, minus the hidden coupling.

Check Yourself

Two questions before you go. First: Hevery's charge is that singletons make APIs into pathological liars. What exactly does a get_instance() call buried in a method body hide, and from whom? Second: your app genuinely needs exactly one connection pool, so someone proposes a singleton. What does constructing it once and injecting it give you that the global accessor doesn't, given that both end with a single instance? Both answers live in The Case Against It.


Then go spot it in the wild. The JDK ships one in plain sight: Runtime.getRuntime(), whose documentation states every Java application has a single Runtime instance that applications cannot construct themselves. Every Python codebase you've touched runs on singletons too, since each imported module is a cached, lazily-created instance shared by every importer. Last, grep a codebase you work on for getInstance and ask of each hit: is the uniqueness load-bearing here, or is this a global that grew a method?

singleton_(lazy,_single-threaded).py

class Logger:
    _instance = None  # the one and only, created on first use

    def __init__(self):
        # Python can't truly lock the constructor; convention says
        # nobody calls Logger() directly
        self.lines: list[str] = []

    @classmethod
    def get_instance(cls) -> "Logger":
        # Lazy init: construct on first call, reuse forever after.
        # NOT thread-safe: two threads can race past the None check
        if cls._instance is None:
            cls._instance = Logger()
        return cls._instance

    def log(self, message: str) -> None:
        self.lines.append(message)

# Example usage
a = Logger.get_instance()
b = Logger.get_instance()
a.log("from a")
b.log("from b")
print(a is b)        # True: both names hold the same object
print(len(a.lines))  # 2: both writes landed in one logger

the_thread-safe_native_idiom.py

# logger.py: the module IS the singleton. Python imports a module
# once and caches it in sys.modules, so every importer gets this
# exact object, lazily, on first import
class Logger:
    def __init__(self):
        self.lines: list[str] = []

    def log(self, message: str) -> None:
        self.lines.append(message)

logger = Logger()

# Any other file:
#   from logger import logger
#   logger.log("from anywhere")  # same object in every module