Insights & examples distilled from *“Learning Patterns” by Lydia Hallie & Addy Osmani***
1 What is a Singleton?
A Singleton is a construct that guarantees exactly one instance of a value exists and offers a global access point to it. The authors highlight that this makes it appealing for application‑wide state, such as configuration or counters.
“Singletons are classes which can be instantiated once, and can be accessed globally.”
Typical use‑case
Logging services, in‑memory caches, shared configuration, feature flags… — anything where multiple parts of the program must coordinate over a single mutable object.
2 Building a Singleton in ES2015+
Below is the Counter example the book walks through. Notice how we block the constructor after the first run and export a frozen instance to prevent accidental mutation:
// counter.js
class Counter {
constructor() {
if (Counter.instance) {
throw new Error("Counter already initialised!");
}
Counter.instance = this;
this.count = 0;
}
getCount() {
return this.count;
}
increment() {
this.count += 1;
}
decrement() {
this.count -= 1;
}
}
const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;
Why freeze? It “reduces the risk of accidentally overwriting values on the Singleton.”
Proof it works
import counter from "./counter.js";
counter.increment(); // 1
counter.increment(); // 2
import counterAgain from "./counter.js";
console.log(counterAgain.getCount()); // 2 — same instance!
Because ES modules are cached, every import receives the same object reference.
3 Alternative: Plain Object Singleton
JavaScript lets us skip the class altogether — we could export a simple object with methods. The authors note this is often simpler and avoids class boilerplate:
// singletonCounter.js
export const counter = {
count: 0,
inc() { this.count++; },
dec() { this.count--; },
};
4 (Dis)Advantages & Pitfalls
✔️ Pros | ❌ Cons / “Anti‑pattern?” |
---|---|
Saves memory – allocate once, reuse everywhere | Hidden dependencies — any module may mutate it, causing spooky side‑effects |
Simple global coordination | Hard to test — state bleeds across test cases unless manually reset |
Encourages implicit global state, which the authors argue is usually better solved with immutable stores (Redux, React Context) |
Because of these downsides, Hallie & Osmani caution that Singletons are often over‑used and label them an anti‑pattern in JavaScript heavy codebases.
5 Singleton vs. Global State Libraries in React
React projects usually reach for Redux or the built‑in Context API when they need cross‑component data. These tools expose read‑only state and controlled mutation flows, avoiding the hidden, mutable globals that plague Singleton designs.
Take‑away: if you simply need to share state, prefer dedicated state‑management utilities. Reach for a Singleton only when you must guarantee exactly one mutable instance and you fully control its access patterns.
6 Checklist — Should I use a Singleton?
- Exactly one instance must exist (e.g., a single WebSocket connection).
- You can enforce clear, documented access to its API.
- Testing strategy includes explicit teardown/reset between tests.
- You’ve ruled out immutable global stores or dependency injection.
If any of these are shaky, reconsider. Patterns are tools, not trophies. Keep the principle of least surprise front and center.
Top comments (0)