Compare
Symptom
Human

Every module that needs a piece of shared state constructs its own copy. The construction is expensive (disk read, network handshake, parser warm-up) and the copies drift the moment any consumer mutates them — but there is no architectural anchor saying which copy is canonical.

Agent

The agent sees `new Config()` scattered across modules and cannot tell from reading whether the consumers share state. Test-isolation reasoning requires the agent to enumerate every construction site; assumptions about 'one config' are unverifiable at static read time.

Goal
Human

Exactly one instance exists for the system's lifetime; every consumer reaches that instance through a single named access point. Expensive setup runs once; the shared state has a canonical home; cross-consumer visibility is intentional, not accidental.

Agent

A single getInstance() access point the agent can grep for to enumerate every consumer. Construction is structurally one-shot; the agent's reasoning about 'who owns this state' has exactly one answer; tests against shared state are explicit about reset.

Before the pattern

class Config {
constructor() {
this.values = loadFromDisk();
}
}
// every consumer constructs its own:
const databaseModule = { config: new Config() };
const httpModule = { config: new Config() };
const loggerModule = { config: new Config() };
// three disk reads; three copies that drift if the file changes;
// no canonical 'current configuration' anywhere in the system.

After the pattern

class Config {
static instance = null;
static getInstance() {
if (Config.instance === null) {
Config.instance = new Config(Config.#TOKEN);
}
return Config.instance;
}
static #TOKEN = Symbol('Config.constructor');
constructor(token) {
if (token !== Config.#TOKEN) {
throw new Error('Use Config.getInstance() — Config is a singleton');
}
this.values = loadFromDisk();
}
}
const databaseModule = { config: Config.getInstance() };
const httpModule = { config: Config.getInstance() };
const loggerModule = { config: Config.getInstance() };
Example source: Illustrative example written for this site in the spirit of Design Patterns (Gamma, Helm, Johnson, Vlissides, Addison-Wesley, 1994), chapter 3. The book's running example is a printer-spooler; this JavaScript adaptation uses a configuration registry where the expensive disk load happens at most once. The private-token guard makes new Config() unconstructable from outside, mirroring the book's private-constructor enforcement in languages that have it.
Pressure
Human

Per-consumer construction is Duplicated Code at the worst level — the work itself is duplicated, not just the source text. Cache invalidation, hot-reload, and disk re-read semantics multiply by consumer count. Drift bugs are catastrophic and slow to diagnose: which Config does this module trust?

Agent

Per-consumer copies multiply the agent's state-graph reasoning by consumer count. Cross-module bugs caused by stale copies are nearly impossible to localize statically — the agent must trace runtime construction order, which is invisible in the source.

Tradeoff
Human

Singletons are Global Data wearing a class costume. They couple every consumer to one concrete implementation; they break unit-test isolation because tests now share mutable state; they hide dependencies behind a static access point that linters can't trace. The pattern is famously over-used; every team has scars.

Agent

Singleton state survives across tests by default; the agent must remember per-test reset discipline that the test framework does not enforce. Coupling consumers to a static getter hides the dependency in a way linters cannot warn about — the agent's 'who depends on what' graph is structurally incomplete.

Relief
Human

One canonical instance; one place to invalidate; one place to log access. Expensive setup runs once. Architectural intent is explicit: 'this is a system-wide shared resource.'

Agent

The instance is constructed at one site (the getter); a grep for the getter returns every consumer; the loading strategy lives at one class the agent edits once to change how the instance is created.

Trap
Human

Singletons multiply: a justified 'just one more' becomes a fleet of twelve. Each new Singleton couples every consumer to it; testing accumulates per-Singleton reset boilerplate; modularity erodes faster than the convenience wins. Stop and reach for explicit dependency injection unless the resource is genuinely process-global (loaded config, system clock, randomness source) and the alternative is materially worse.

Agent

When tests rely on Config.getInstance() returning a real (live) instance, the agent's edits to Config silently break unrelated tests through the shared-state coupling. The pattern's convenience hides exactly the kind of cross-cutting dependency the agent needs structural visibility into.