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.
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 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.
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.
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.
When the pattern genuinely fits, building it is four moves:
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.Logger that will ever exist lives inside the Logger class definition itself.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.sync.Once to Rust's LazyLock.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.
The pattern is three lines of mechanism and twenty years of operational lessons:
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?
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# 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