← System Design
☝️
System Design

Singleton

One instance, shared by everyone. Useful for config and connection pools — dangerous everywhere else.

TL;DR

Singleton guarantees a class has exactly one instance and provides a global access point to it. It's perfect for things that genuinely should be one — config registries, database connection pools, logger sinks. It's a disaster when used as a convenient global — it tightly couples every caller, kills testability, and silently breaks under concurrency or in distributed deployments.

When to use

Use Singleton when a single instance is a *correctness* requirement (one connection pool, one configuration, one cache shared per process). Avoid it as a "convenience global" — if multiple instances would still work, you don't need Singleton, you need dependency injection.

Try it

When Singleton is the right call

  • Connection pools — opening a new database connection per call would exhaust the database.
  • Configuration — every component reads the same config; mutating it from two different copies would be a nightmare.
  • Shared in-memory cache — by definition there’s one cache per process.
  • Logger — appending to a single sink, often with a buffered writer.

When Singleton is the wrong call

  • As a global parameter passer. “I’ll just stash the current user in a singleton” → now every method secretly depends on it, you can’t test, and concurrent requests overwrite each other.
  • For business logic. A OrderService singleton is just a static class with extra steps. Use DI.
  • When you might want two for testing or multi-tenancy. Hard-coded singletons can’t be partitioned.

Implementation pitfalls

Thread safety — naive lazy init is racy. Use one of:

  • Eager init: private static final Foo INSTANCE = new Foo();
  • Holder idiom (JVM): nested class loaded only on first access.
  • Double-checked locking with volatile.
  • Language built-ins: Kotlin object, Python module-level constants.

Subclasses & serialization — both can break the “one instance” guarantee unless you explicitly handle them.

The system-design connection

In a single-process app, Singleton enforces “one shared resource.”

📺 Video

A YouTube walkthrough — coming once the deep-dive video is published.

🧪 Simulator

An interactive simulator for this concept is on the way — tweak the knobs, watch behaviour change in real time.

💻 Code

A 30-line build challenge with starter code, hints, and a reference implementation.

🎯 Common interview questions
Q1. What's wrong with `if (instance == null) instance = new Foo();`?

It's not thread-safe. Two threads can both pass the null check before either assigns, and you end up with two instances. Fix it with a lock — or better, use eager initialization (`static final Foo instance = new Foo();`) which the JVM/CLR guarantees runs once and is visible to all threads.

Q2. Why do testers hate Singletons?

Because they're hidden global state. A test that runs after another test inherits whatever the first test mutated on the singleton. Tests stop being isolated, ordering becomes load-bearing, and flaky tests creep in. The fix is dependency injection — pass the dependency in, so each test can hand in a fresh fake.

Q3. How does Singleton break in a distributed system?

It doesn't survive process boundaries. "One instance per JVM" becomes "N instances across N pods," and any state you stored in it is per-pod. If you genuinely need one instance cluster-wide (leader election, distributed lock), use Zookeeper / etcd / a database row — Singleton is a *language* construct, not a *system* construct.

Q4. Is dependency injection just a fancier Singleton?

No — DI containers can give you a singleton *lifetime* for a service, but the key difference is the dependency is explicit (passed in via constructor) instead of implicit (fetched via `Foo.getInstance()`). That makes it swappable for a fake in tests and visible in the constructor signature.

Q5. Are static utility classes Singletons?

Effectively yes — they share the same global-state problems. If `MathUtils.calculate(...)` is pure (no state), static is fine. If it touches I/O, time, or shared state, you have all the testability problems of a Singleton without even the option to swap it.

↗ Related concepts

Comments 0

Discuss this page. Markdown supported. Be kind.

Loading…
Loading comments…