Creational pattern
Singleton
One class controls its own single instance and hands the exact same object to everyone who asks for it — no matter how many times they ask.
✗ The problem
Every caller makes its own instance
Nothing stops two parts of the app from each doing new DbPool().
Now there are two pools, two connection lists, two sources of truth.
class DbPool {
constructor() { this.connections = []; }
}
// two different callers…
const a = new DbPool();
const b = new DbPool();
a === b; // false ✗ two pools!
pool A
new DbPool()
pool B
new DbPool()
Wasted connections, inconsistent state — each caller thinks it owns
the pool, but really owns a pool.
✓ The pattern
↓
The class controls its own single instance
A private static slot holds the one instance. A static accessor creates it lazily on first call, then always returns that same object.
class DbPool {
static #instance = null;
static getInstance() {
if (!DbPool.#instance) {
DbPool.#instance = new DbPool();
}
return DbPool.#instance;
}
}
Caller A
getInstance()
Caller B
getInstance()
DbPool
the one instance
✓ See it live
Same id, every time — unless you bypass it
getInstance() always resolves to the same instance id.
new DbPool() mints a fresh one each time — that's the bug it fixes.
DbPool.#instance
not created yet
0 calls
Green = same shared instance. Red = a brand new, disconnected object.
✓ Takeaway
One instance — but at a real cost
- Guarantees a single shared instance and one global access point to it.
- Lazy: the instance is created only on first
getInstance()call. - It's global mutable state wearing a class name — any code, anywhere, can read and change it, creating hidden dependencies between unrelated modules.
- Hard to test: shared state leaks between unit tests unless you manually reset it.
- Concurrency pitfalls: two threads/requests can race on the lazy-init check.
⚠ Modern advice: prefer Dependency Injection — construct the
single instance once at startup and pass it in — over reaching for a hard Singleton.
You keep the "one instance" benefit without the hidden global coupling.
🎯 Principle applied: Singleton sits in tension with the principles: global access fights Dependency Inversion and hurts testability. The principled alternative is usually Dependency Injection — create one instance and pass it in — keeping the benefit without the global.
Back to all topics →