The Singleton Pattern
Introduction
The Singleton Pattern ensures that a system has exactly one shared instance of something and provides a controlled way to access it. In JavaScript, this can be implemented with closure state, module-level state, or a factory that caches its first result.
Interviewers expect you to implement lazy initialisation, explain why every call returns the same object, and discuss why singletons can make tests harder when they hide mutable global state.
Why This Matters
Singletons appear in real applications as configuration stores, analytics clients, loggers, caches, feature-flag clients, and connection managers. The interview-grade answer is balanced: you can implement one, but you also understand the coupling and testing costs.
Theory
Core idea
A singleton has one instance for the lifetime of a runtime. Instead of calling a constructor repeatedly, callers use an accessor such as getInstance(). The accessor creates the instance once, then returns the cached instance on future calls.
Eager vs lazy
- Eager singleton: create the instance immediately when the module loads.
- Lazy singleton: create the instance only on the first
getInstance()call.
Lazy initialisation is common in interviews because it proves you can store private state in a closure and guard creation.
JavaScript-specific note
ES modules are evaluated once per runtime and cached by the module loader, so a module-level object can behave like a singleton. That is convenient, but it is still shared mutable state. Treat it carefully.
Pros
- One source of truth for shared configuration or resources.
- Avoids repeatedly constructing expensive objects.
- Gives a central access point.
Cons and testing pitfalls
- Hidden global state makes test order matter.
- State can leak between tests unless you provide a reset hook or inject dependencies.
- Consumers become tightly coupled to the singleton accessor.
- Overuse turns ordinary state into application-wide state, which makes reasoning harder.
Use singletons sparingly and prefer explicit dependency injection when code needs to be easy to test.
Visual Diagrams
caller A ----+
|
caller B ----+--> getInstance()
| |
caller C ----+ +-- first call creates object
|
+-- later calls return same objectThe accessor owns the cached reference and controls when the object is created.
Code Examples
Closure-based lazy singleton
The private instance variable is created once and reused.
Generic singleton factory
A reusable helper can turn any creation function into a lazy singleton accessor.
Playground
Press Run to execute the code and see output here.
Output Prediction
Predict the output #1
1const registry = (function () {2 let instance;3 let createdCount = 0;4 5 return {6 getInstance: function () {7 if (!instance) {8 createdCount += 1;9 instance = {10 id: createdCount11 };12 }13 14 return instance;15 }16 };17})();18 19const first = registry.getInstance();20const second = registry.getInstance();21 22first.name = 'primary';23 24console.log(first === second);25console.log(second.name);26console.log(second.id);Coding Exercises
Implement a lazy singleton accessor
MediumImplement createSingleton(createValue). It should return a function. The returned function calls createValue() only the first time and returns the same value for every later call, even if the created value is falsy.
Interview Questions
1How do you implement a singleton with lazy initialisation in JavaScript?
Keep a private instance variable in module scope or closure scope. Expose getInstance(). On the first call, create the object and store it in instance; on later calls, return the cached reference. If the created value can be falsy, track creation with a separate boolean flag.
Follow-ups
- How would you reset it in tests?
- How do ES modules behave like singletons?
2Why are singletons controversial?
They are convenient for shared resources, but they hide global mutable state. That can create tight coupling, make test order matter, leak state between tests, and make dependencies less explicit. A strong design often injects a dependency rather than having every consumer call a global singleton accessor.
Quiz
1. Why should a robust singleton accessor use a separate `created` flag?
2. What is a common downside of singletons?
Summary
- A singleton exposes one shared instance through a controlled access point.
- Lazy initialisation creates the instance on the first access and caches it afterward.
- JavaScript can implement singletons with closures or module-level state.
- Singletons are useful for shared resources but can create hidden coupling and test pollution.
Cheat Sheet
Shape: private instance + public getInstance().
Lazy: create on first call, return cached reference later.
Robust flag: use created when the instance may be falsy.
Good for: config, logging, caches, expensive shared clients.
Risk: hidden global mutable state; prefer dependency injection for testable code.