skip to content

Given a singleton requirement, how would you decide between eager static init, the holder idiom, volatile double-checked locking, and an enum — and what trade-offs drive the choice?

level: principalimportance: nice to knowfreq 45%

answer

  1. Decide on: laziness needed? runtime args needed?
  2. Not lazy → enum (safest) or eager static (simplest)
  3. Lazy, no args → holder idiom
  4. Lazy + constructor args → volatile DCL only
  5. Prefer JVM-guaranteed safety over hand-rolled ordering

basics

~20 s

If the object is cheap or always needed, use an eager static field (or an enum for the safest singleton). If you need lazy creation, prefer the holder idiom. Only reach for volatile double-checked locking when you need lazy init but the constructor requires runtime arguments the holder idiom can't pass.

solid answer

~50 s

I start from whether laziness is actually required. If the object is cheap, always needed, or fine to build at startup, eager static initialization is simplest, and a single-element enum is the most robust singleton — concise, serialization-safe, and reflection-safe per Effective Java. If I genuinely need lazy initialization (expensive or rarely-used object), the initialization-on-demand holder idiom is my default: it's lazy and thread-safe via the JVM's class-init guarantee, with no volatile or synchronized to get wrong and no hot-path lock. I only fall back to volatile double-checked locking when construction needs runtime parameters that a static initializer can't supply, accepting its extra subtlety. In a DI/Spring world I'd usually delegate lifecycle to the container's singleton scope and avoid hand-rolling any of this. The governing trade-offs are startup cost vs. memory/resource footprint, correctness risk of hand-written lock-free code, serialization/reflection safety, and testability.

go deeper

for a junior

Knows the four options exist and that eager static or enum is the simple safe default when laziness isn't required.

for a middle

Can match each option to a scenario and explain why the holder idiom is the usual lazy choice over DCL.

for a senior

Justifies the choice on concrete trade-offs (startup vs footprint, correctness risk, serialization/reflection safety) and knows DCL's one unique niche.

for a principal

Drives a team standard favoring JVM-guaranteed idioms, folds in DI-container lifecycle and testability, and articulates why hand-rolled lock-free code is a liability outside its narrow justified use.

## Frame the decision around laziness and construction needs A **singleton** guarantees exactly one instance of a type. The four mainstream Java approaches differ on *when* the instance is built and *how* thread-safety is achieved. Pick by answering two questions: (1) Do I truly need **lazy** initialization? (2) Does construction need **runtime arguments**? ### 1. Eager static initialization ```java private static final Service INSTANCE = new Service(); public static Service getInstance() { return INSTANCE; } ``` The field is built when the class is initialized. **Thread-safe for free** (the JVM class-init guarantee). Choose it when the object is cheap, always needed, or acceptable to build at startup. Downsides: pays construction cost even if unused, and the instance lives for the program's life. ### 2. Enum singleton (Effective Java's recommendation) ```java public enum Service { INSTANCE; /* methods */ } ``` A single-element enum is the **most robust** singleton: concise, and uniquely **serialization-safe** (no extra `readResolve` needed) and **reflection-safe** (you can't construct a second instance via reflection — the JVM forbids reflective enum construction). It is **eagerly** initialized when the enum class loads, so it's not for cases that demand laziness. ### 3. Initialization-on-demand holder idiom (the lazy default) Put the instance in a private static nested class; the JVM initializes that nested class only on first use, lazily and thread-safely. **No `volatile`, no `synchronized`** in your code, **no hot-path lock**. This is the go-to when you need laziness and construction needs no runtime arguments. It can't take constructor parameters and turns initialization failures into class-init errors. ### 4. Volatile double-checked locking (DCL) ```java private static volatile Service instance; public static Service getInstance() { Service r = instance; if (r == null) synchronized (Service.class) { r = instance; if (r == null) instance = r = new Service(args); } return r; } ``` Lazy, lock-free on the fast path, and — unlike the holder idiom — it **can pass runtime arguments** into the constructor (`getInstance(args)`). The price is real: it is the easiest of the four to get wrong (the field **must** be `volatile`, or you risk publishing a partially constructed object), so reserve it for the specific case where the holder idiom can't express the construction. ## The decision in one path - Need DI/framework lifecycle? → Let the container own it (e.g. Spring singleton scope); don't hand-roll. - Don't need laziness? → **Enum** (safest) or **eager static** (simplest). - Need laziness, no constructor args? → **Holder idiom**. - Need laziness *and* runtime constructor args? → **Volatile DCL** (only then). ## Cross-cutting trade-offs - **Startup cost vs. footprint:** eager pays upfront and holds resources; lazy defers cost but adds the first-call init. - **Correctness risk:** holder/enum/eager rely on JVM guarantees you can't misuse; DCL relies on you remembering `volatile`. - **Serialization/reflection safety:** only the enum is bulletproof out of the box; the others need care (`readResolve`, private-constructor guards) if those vectors matter. - **Testability:** static singletons are hard to substitute in tests; prefer dependency injection where feasible, which often makes the whole question moot. ## Bottom line Favor the strategy whose safety comes from a JVM guarantee (enum, eager static, holder idiom) over one whose safety depends on hand-written memory ordering (DCL). Use DCL only for its one unique capability — lazy init with runtime constructor arguments.

  • What unique capability justifies choosing DCL over the holder idiom?
    Passing runtime arguments into the constructor on first creation (e.g. getInstance(config)). A static field initializer in the holder idiom takes no parameters, so when lazy construction must consume runtime state, volatile DCL is the fit — otherwise prefer the holder idiom.
  • Why is the enum singleton called the safest?
    It is serialization-safe without a readResolve method and reflection-safe because the JVM forbids reflective construction of enum constants, so you cannot accidentally or maliciously create a second instance. Its limitation is that it is eagerly initialized, so it isn't for cases requiring laziness.
  • How does dependency injection change this decision?
    A DI container (e.g. Spring) manages object lifecycle and can provide a single shared instance via singleton scope, with lazy options configurable. That usually removes the need to hand-write any of these patterns and improves testability by letting you inject substitutes.

saying these in an interview costs you the question

  • Defaulting to DCL everywhere when the holder idiom or an enum is simpler and safer
  • Claiming the enum singleton can be lazy (it's eager at class load)
  • Ignoring serialization/reflection attacks for non-enum singletons when they matter
  • Hand-rolling a singleton inside a DI framework that already manages singleton scope

context