Deep Dive
low level designdesign patternsoop

Singleton: One Instance, and the Global It Smuggles In

The GoF pattern everyone knows and half of us regret. Watch a naive getInstance() spawn two instances under load — then see why modern apps get one instance from a DI container, not a global.

·20 min read
Easy

A service reads its feature flags from a Config object. Somewhere in a refactor, two code paths each did new Config() — the checkout flow built one, a background job built another. For months nothing broke, because the two copies happened to agree. Then an on-call engineer flipped a flag at 2 a.m., checkout picked it up, and the background job kept running the old value. Orders reconciled against stale rules for six hours before anyone found the second Config.

Two instances of a thing that was only ever supposed to be one is a specific, nasty class of bug. It shows up wherever a second copy is not just wasteful but incoherent: two connection pools each opening the max connections and exhausting the database; two caches that disagree; two loggers interleaving half-lines into the same file. When one instance is the invariant, you want the type system — not a code review — to enforce it.

Read that intent carefully, because it does two things, and the second one is where the trouble lives. “One instance” is a legitimate constraint. “A global point of access” is a global variable. Singleton is the most divisive pattern in the whole GoF catalog precisely because it bundles a reasonable goal with a mechanism many teams have learned to distrust. We’ll give both halves an honest hearing.

The intuition: the thing there is only one of

Some resources are singular by nature. A building has one main electrical panel. An operating system has one process table. A game has one audio mixer. You don’t want two of these not because two is expensive, but because “two” doesn’t mean anything — a second source of truth that can drift out of sync with the first.

From here the members-only continuation runs the whole ladder: an interactive concurrency model that manufactures a rogue second instance under load, the implementations from naive-lazy through double-checked locking, the holder idiom, and Bloch’s enum — with the runnable code for each — then the honest critique of the global access point, the DI-container alternative that keeps the one-instance guarantee without it, an interview-corner double-checked-locking bug hunt, and a quiz to check yourself.

Members only

Keep reading with Premium

You've reached the members-only part of this deep-dive — the full implementation, the interactive ring simulator, and the step-by-step walkthrough. Unlock it with a membership.

Related Articles