LLD Dojo

Singleton

Core. Expect to meet this one, and expect to be asked for it by name.

Start with the problem

A parking garage has several entry gates, each printing tickets, and no two tickets anywhere in the building may share a number. The obvious place to keep the next number is a static field on the class that prints tickets.

final class TicketPrinter {
    private static long nextTicketNumber = 1;

    long printTicket() {
        return nextTicketNumber++;
    }
}

Every gate calls new TicketPrinter().printTicket(), and every call anywhere in the process sees the same counter, because the field belongs to the class, not to any one printer.

Watch where it goes

A second requirement makes the shared state explicit rather than incidental: the counter also has to survive being read from two gates at once without ever handing out the same number twice. That is a real correctness requirement now, not a convenience, and nextTicketNumber++ on a plain long is not safe under two threads.

The deeper question is not the counter's type, though. It is whether there should be more than one TicketPrinter in memory at all. If a second one could exist, it would start counting from 1 again, independent of the first, and the "no two tickets share a number" rule would already be broken by construction. So the requirement is not really about incrementing safely. It is that exactly one counter must exist for the life of the process, and nothing in the code so far says so.

The move

State the one-instance rule as a type, rather than as a habit every caller has to remember.

enum TicketPrinter {
    INSTANCE;

    private final AtomicLong nextTicketNumber = new AtomicLong(1);

    long printTicket() {
        return nextTicketNumber.getAndIncrement();
    }
}

TicketPrinter.INSTANCE.printTicket() is the only way to reach a counter, from any gate, and the enum's own construction rules guarantee there is exactly one INSTANCE for the life of the JVM. AtomicLong replaces the unsafe long, since two gates now really do call this at once.

What modern Java changes here

The classic version of this pattern is a private constructor plus a static getInstance() that lazily builds and caches one object, often guarded with synchronized to survive concurrent first calls. An enum with one constant gets the same guarantee directly from the language. The class loader constructs INSTANCE exactly once. There is no second constructor to call by accident, either, since an enum's constructors are implicitly private. Effective Java recommends the enum form for exactly this reason, and it is close to the only shape worth writing today.

Whether a single-instance rule belongs in the type at all is the harder question, and it is worth asking before reaching for either form. Most things that look like "there is only one of these" are really "the caller only ever constructs one." A single object, built once at start-up and passed down to whatever needs it, already gives that, with no enum and no static field. Reserve the enum form for cases where a second instance existing would itself be a bug, the kind the type should make impossible, the way a second TicketPrinter genuinely would be here.

When naming it is wrong

corpus/logger's DECISION_LOG.md records a logger built the same way several public write-ups of that exact problem build it: a single static getInstance() shared by every caller. It was rejected there for three concrete reasons. It cannot be constructed twice with different collaborators, so two independent tests in the same run cannot each get their own clock. It also hides a real dependency instead of naming it in a constructor signature. That is the same dependency-inversion violation this app's syllabus item C3 names, where a rate limiter reads Instant.now() directly instead of taking a Clock. And it cannot be swapped for a test double without a classloader trick that would test the trick, not the logger.

The threshold: a single instance enforced by the type itself is right only when a second instance existing would itself be the bug, the way two independent ticket counters would double-issue numbers. Sometimes the real requirement is smaller: one clock, one logger, one config, built once and handed to whoever needs it. That is composition done at start-up, not a rule the class should enforce on itself. Reaching for enum INSTANCE there buys untestable global state for a problem a constructor argument already solved. The Standard's D3 dimension scores that as a seam that should not have existed.

Where this lives in the app

corpus/logger's DECISION_LOG.md argues the rejection in full, under the heading "Rejected alternative: Logger as a singleton," and syllabus item C3 builds the constructor-injection alternative out against corpus/rate-limiter's Clock.

All reference pages