LESSON 4.2
Singleton
Ensure a class has only one instance and provide a global point of access to it, while understanding when dependency injection is superior.
12 min read
Real-world analogy
A country has only one official national central bank or federal mint. While thousands of commercial banks and millions of citizens interact with monetary policy, all operations resolve back to that single monetary authority. Creating multiple competing central banks would cause inconsistent currency reserves and uncontrollable financial conflicts.
The problem
Imagine a telemetry logging system or database coordinator in a server application. If multiple components independently instantiate their own database pool or logger without coordination, each creates duplicate connection threads, file handles, and conflicting buffers.
In the naive implementation below, different callers create separate instances that inadvertently overwrite each other's state or waste system resources:
class DatabasePool {
#connectionCount = 0;
constructor() {
this.#connectionCount = 5;
console.log("Allocating 5 new database connections...");
}
query(sql) {
return `Executing "${sql}" on pool with ${this.#connectionCount} conns`;
}
}
// OrderService creates its own pool:
const ordersPool = new DatabasePool();
// PaymentService creates yet another pool:
const paymentsPool = new DatabasePool();
console.log(ordersPool === paymentsPool); // false: redundant pools created!public class Example {
static class DatabasePool {
private final int connectionCount;
public DatabasePool() {
this.connectionCount = 5;
System.out.println("Allocating 5 new database connections...");
}
public String query(String sql) {
return "Executing
The pain is twofold: excessive resource consumption from duplicate allocations, and inability to maintain coherent global invariants (such as global transaction sequencing or bounded connection limits).
The solution
Intent: Ensure a class has only one instance and provide a global point of access to it.
The participants are:
- Singleton: Declares a static method (commonly
getInstance) that returns the solitary instance, keeps its constructor private to forbid directnewinvocations, and stores the cached instance in a private static field. - Client: Calls
Singleton.getInstance()to access the instance rather than invoking a constructor directly.
Step-by-step implementation
Step 1: Make constructor private
Prevent external code from using new directly. The class must be the sole authority deciding when instantiation happens.
Step 2: Add static instance holder and accessor
Introduce a static variable to cache the instance and a public static accessor method.
Step 3: Implement thread safety and double-checked locking
In multithreaded runtimes like Java, naive lazy initialization suffers from race conditions where two threads concurrently see instance == null and allocate two distinct objects. Synchronizing the entire method adds severe lock contention on every subsequent read. The solution is double-checked locking with a volatile reference.
The complete example
Here is a complete, runnable database connection coordinator that maintains a solitary connection pool and executes queries deterministically across multiple callers.
Same instance: true
Result for [SELECT * FROM users] (active: 1)
Result for [UPDATE accounts SET balance = 500] (active: 1)Variations
In production and interviews, singletons appear in several distinct forms:
1. Eager initialization
The instance is created at class-loading time:
private static final DatabasePool INSTANCE = new DatabasePool();
public static DatabasePool getInstance() { return INSTANCE; }- Pros: Inherently thread-safe; zero synchronization overhead.
- Cons: Instantiated even if the application never uses it, potentially wasting startup time and memory.
2. Lazy double-checked locking with volatile
As implemented in Step 3. The volatile keyword is mandatory in Java because without it, CPU instruction reordering can write the reference to instance before memory for the object's fields is initialized, exposing a half-baked object to other threads.
3. Initialization-on-demand holder idiom (Bill Pugh)
Leverages the JVM's class loader guarantees. The nested static class is not loaded until getInstance() is called:
public class DatabasePool {
private DatabasePool() {}
private static class Holder {
static final DatabasePool INSTANCE = new DatabasePool();
}
public static DatabasePool getInstance() {
return Holder.INSTANCE;
}
}- Pros: Lazy loading without any explicit
synchronizedblock orvolatilekeyword.
4. Enum singleton (Java)
Recommended by Joshua Bloch in Effective Java:
public enum DatabasePool {
INSTANCE;
public String query(String sql) { return "Executing " + sql; }
}- Pros: 100% immune to reflection attacks (
Constructor.setAccessible) and serialization traps (deserialization creates a duplicate unlessreadResolveis provided).
5. Module singleton (JavaScript)
In modern JavaScript (ES Modules and CommonJS), modules are evaluated once and cached:
// db-pool.js
class Pool { /* ... */ }
export const pool = new Pool();Every file that imports pool receives the exact same cached object reference without static class mechanics.
6. Dependency injection (The modern alternative)
Rather than consumer classes calling DatabasePool.getInstance(), the DI container creates one instance at application startup and injects it into constructors (@Autowired or constructor arguments). This retains single-instance semantics without coupling callers to a global static accessor.
Where you've already used it
- Java Development Kit (JDK):
java.lang.Runtime.getRuntime(): Exposes process memory, processor counts, and shutdown hooks for the running JVM.java.awt.Desktop.getDesktop(): Provides access to the host operating system's desktop environment.
- Node.js runtime:
process: Global singleton providing current process metrics, environment variables (process.env), and exit handlers.- Module cache (
require.cache): Module exports are evaluated once and returned as singletons to all importers.
- Frontend & React:
- Single root Redux store or Zustand store passed down through a React Context Provider.
- Spring Framework:
- Spring Beans default to singleton scope (
@Scope("singleton")), meaning the Spring IoC container creates and manages exactly one bean instance per application context.
- Spring Beans default to singleton scope (
Pros and cons
| Strengths | Weaknesses |
|---|---|
| Guaranteed solitary instance prevents resource conflicts | Introduces hidden global state across the entire codebase |
| Lazy initialization defers expensive setup until first use | Tightly couples consumers to concrete class names |
| Controlled global access point eliminates duplicate config | Breaks unit test isolation; tests leak state across suites |
| Memory savings compared to redundant allocations | Violates Single Responsibility (controls lifecycle AND logic) |
When not to use it
Do not use Singleton purely as a convenient shortcut for passing data. If multiple parts of your application need a database connection, configuration reader, or cache client, pass the dependency explicitly via constructor injection. Use Singleton only when duplicate instances would break core system correctness.
Related patterns
| Pattern | Relationship to Singleton |
|---|---|
| Factory Method | Often implemented as a singleton factory when only one creator instance is needed. |
| Abstract Factory | Abstract factory implementations are commonly singletons because one factory coordinates the entire UI or cloud family. |
| Prototype | Polar opposite: Prototype creates fresh clones of existing objects, while Singleton guarantees no new instances are ever created. |
| Facade | Facades are often singletons because a single simplified entrypoint is sufficient for an entire subsystem. |
Singleton: configuration manager
Refactor a configuration manager that creates multiple isolated instances into a true Singleton that shares a single global configuration state across all modules.
Click Run tests to evaluate your solution against the behavior and structural test suite.
Interview questions
Q1: How do you prevent reflection from breaking a Java Singleton?
In the private constructor, throw an IllegalStateException if the instance reference is already non-null. Alternatively, implement the singleton as a Java enum, which the JVM runtime explicitly forbids reflection from instantiating.
Q2: What is the issue with serializing and deserializing a Singleton?
Deserialization creates a brand new object on the heap, bypassing the private constructor and violating the solitary instance invariant. To prevent this, implement the readResolve() hook in Java and return the existing getInstance().
Q3: Why is Singleton considered a testing anti-pattern?
Because Singleton.getInstance() is a static method call hardcoded inside consumer methods, test runners cannot substitute a mock or stub without bytecode manipulation or global reset methods. State accumulated in test A leaks into test B, causing non-deterministic test failures.
Q4: What is the difference between Singleton and a class with only static methods?
A Singleton is a true object on the heap: it can implement interfaces, be passed as an argument, support lazy initialization, and participate in polymorphism. A class with static methods cannot implement an interface or be passed polymorphically.
Try it
Logging Framework LLD
Compose structured logging, redaction and bounded asynchronous output.