skip to content

"Exactly one instance" — one per what? What boundaries actually limit a Singleton's uniqueness guarantee, and what can silently break it?

level: seniorimportance: should knowfreq 45%

answer

  1. one per class loader, not per universe
  2. N replicas ⇒ N singletons
  3. reflection / deserialization / clone / hot reload break it
  4. enum or language object = runtime-enforced single
  5. cross-node uniqueness needs a lock or leader election

basics

~20 s

One per isolated runtime unit — normally one process (and, in some runtimes, one class loader or module instance). Run two processes, replicas, or workers and you have several. Reflection, deserialization, cloning, and code reloading can also create extras inside one process.

solid answer

~50 s

The pattern guarantees one instance per *initialization unit*, which is typically one process, and in some runtimes narrower: one per class loader or module instance, so the same class loaded twice yields two independent singletons — a routine occurrence in plugin systems, application servers, and isolated test class loaders. Nothing about the pattern crosses a process boundary, so horizontally scaled services, multi-worker servers, forked child processes, and serverless instances each get their own. Within one process the guarantee can also be defeated: reflective access to a private constructor, deserialization creating a fresh object, cloning, and hot-reload or dynamic reloading of the defining class. Language-level guards help — using a language construct that is unique by definition, or having deserialization return the canonical instance — but the deeper point is that if uniqueness is a *correctness* requirement across nodes, you need external coordination (a distributed lock, leader election, a uniqueness constraint), never a code pattern.

code

pseudocode · 14 lines
pseudocode
// Uniqueness is only as wide as the static storage

// class loader A          class loader B
// Counter.instance = #1    Counter.instance = #2   <-- two singletons, one process

// deserialization bypasses the constructor
bytes    = serialize(Counter.getInstance())
restored = deserialize(bytes)          // a NEW object unless a hook intervenes
assert restored == Counter.getInstance()   // fails without readResolve-style handling

// defensive constructor
private Counter() {
    if (instance != null) throw new IllegalStateException("already constructed")
}

go deeper

for a junior

Say one instance per running program, and that several processes or servers each get their own.

for a middle

Add the class-loader/module boundary and name the in-process escapes: reflection, deserialization, cloning.

for a senior

Discuss what breaks in a replicated deployment (counters, schedulers, rate limiters, caches), the enum/language-object defence, and hot-reload effects.

for a principal

Separate uniqueness-as-optimisation from uniqueness-as-correctness; require external coordination (leader election, fencing tokens, store constraints) for the latter and prefer designs that are idempotent or partitioned so uniqueness is never needed.

## "One instance" is always relative to a scope The pattern's enforcement mechanism is class-level (static) storage. Therefore uniqueness holds exactly as far as *that storage* is shared — and no further. ### Boundary 1 — the class loader / module instance On runtimes that can load the same class definition more than once (JVM class loaders, .NET assembly load contexts, plugin sandboxes, some JS module-registry setups), each load gets its **own copy of the static fields**. Two loads ⇒ two singletons. Where this bites: - Application servers deploying several applications, each with its own loader. - Plugin/extension frameworks that isolate plugins. - Test runners that use isolated loaders per test class, so "reset the singleton" behaves unpredictably. - Shading/vendoring the same library under two coordinates so both copies are present. A notorious symptom: a `ClassCastException` (or equivalent) between two instances of what *looks like* the same class, or configuration set on one copy being invisible to code holding the other. ### Boundary 2 — the process Static storage lives in one address space. Consequences: - A service running **N replicas** has N singletons. Any in-memory cache, counter, rate limiter, or scheduler built as a singleton silently becomes per-replica. - **Multi-process web servers** (pre-fork workers) get one per worker. - **Serverless** functions get one per warm execution environment — the classic "my in-memory cache mostly works but sometimes misses" mystery. - A **forked child** gets a copy-on-write duplicate of the parent's state; the child now has a *distinct* instance holding a snapshot — dangerous for anything holding sockets or file descriptors. This matters most for singletons whose correctness assumes global uniqueness: an ID generator issuing sequential IDs, a cron-style scheduler expecting to run a job once, a rate limiter enforcing a global quota. Under replication each of these becomes wrong, not merely inefficient. ### Boundary 3 — the machine/cluster No code pattern gives cross-node uniqueness. That requires **coordination**: leader election, a distributed lock with a lease and fencing token, a database uniqueness constraint, or a partitioned scheme (per-node ID prefixes) that removes the need for uniqueness. Interviewers like to hear that distinction stated crisply. ## In-process ways the guarantee is defeated 1. **Reflection**: many runtimes let privileged code make a private constructor accessible and invoke it. Defence: have the constructor throw if the instance already exists, or use a construct the runtime itself protects. 2. **Deserialization**: reconstructing an object from bytes typically bypasses the constructor and yields a brand-new object — so a serialized singleton round-trips into a *second* one. Defence: a hook that returns the canonical instance instead of the freshly read one (e.g. a readResolve-style callback). 3. **Cloning**: a default/shallow clone facility can duplicate the instance. Defence: refuse to clone. 4. **Class reloading / hot reload**: dev-time reloaders and OSGi-style dynamic modules discard and reload classes, resetting or duplicating static state. This is why "it works in prod but the dev server behaves oddly after a reload" reports exist. 5. **Multiple accessors or subclassing** in a sloppy implementation that lets a subclass build another instance. **Enum-based or language-object singletons** (a Java `enum` with one constant, a Kotlin `object`) close most of these at once: the runtime guarantees a single instance, serialization is identity-preserving, reflective instantiation is refused, and initialization is thread-safe. The trade-off is reduced flexibility — awkward inheritance and, for enums, eager creation. ## Design consequences - **State that must be globally unique does not belong in a singleton.** Move it to a store with real uniqueness semantics. - **Per-process singletons are fine for per-process concerns**: a connection pool, a metrics registry that is later aggregated, a local cache with a TTL, a thread pool. - **Write down the scope.** "One per process" in the class doc prevents the whole family of bugs where someone assumes it is one per cluster. - **Tests**: if uniqueness is per class loader and your runner isolates loaders, cross-test state leakage becomes intermittent and infuriating. Prefer injected instances so each test builds its own. ## Quick self-check when reviewing a singleton 1. If a second instance existed, would anything be *wrong*, or merely wasteful? 2. Does correctness assume there is only one *in the whole system*? If yes, the pattern cannot deliver it. 3. Is the class ever serialized, cloned, or reflectively constructed? 4. Does the deployment run multiple processes, workers, or replicas?

  • Your singleton generates sequential IDs and the service is scaled to five replicas. What breaks and what would you do instead?
    Each replica has its own counter, so IDs collide across replicas. Replace it with a source of genuine global uniqueness: a database sequence, a UUID/ULID, or a partitioned scheme where each node is assigned a distinct prefix or block so no coordination is needed on the hot path.
  • Why does serializing and deserializing a singleton typically produce a second instance?
    Deserialization reconstructs the object from its byte form without invoking the class's constructor, so the private-constructor enforcement is bypassed entirely. Preventing it requires an explicit hook that discards the newly read object and returns the canonical instance, or using a language construct whose serialization is identity-preserving.
  • How do you enforce that an action happens only once across a whole cluster?
    With external coordination, not a code pattern: leader election, a distributed lock with a lease and a fencing token to survive pauses, or an idempotency/uniqueness constraint in a shared store so duplicate attempts are rejected rather than prevented. Design the action to be idempotent so an occasional duplicate is harmless.

"One key to the office" is only true per building. Open a second branch and there are two keys, each perfectly unique in its own building. And even inside one building, a locksmith (reflection), a photocopied key from an old impression (deserialization), or a renovation that re-fits the locks (hot reload) quietly produces another.

saying these in an interview costs you the question

  • "Singleton guarantees one instance in the system" — it guarantees one per process, sometimes only per class loader.
  • Building a scheduler, global rate limiter, or sequential ID source as an in-memory singleton in a replicated service.
  • Forgetting that deserialization and reflection bypass a private constructor.
  • Assuming an in-memory singleton cache is coherent across workers or serverless instances.
  • Trying to achieve cluster-wide uniqueness with clever code instead of leader election or a store-level constraint.
  • Ignoring that isolated class loaders in a test runner make singleton state leakage intermittent rather than reproducible.

context