What Problem It Solves
Ensure a class has exactly ONE instance and provide a global point of access.
Common Interview Traps
| Trap | Why | Fix |
|---|---|---|
Forgetting volatile in Java | A thread may see a partially constructed instance | Use volatile + double-checked locking, or the holder-pattern idiom |
Creating a new instance in getInstance() every time | Breaks the singleton guarantee | Use static final field or __new__ override |
| Making singleton a God class | Singleton has too many responsibilities | Singleton only ensures single instance โ it's still SRP |
Where Singleton Appears in LLD Problems
| Problem | Singleton Class | Why |
|---|---|---|
| Parking Lot (#1) | ParkingSpotManager | Central spot allocation across all entry gates |
| Logger (#15) | LogManager / Logger | Single log queue to avoid interleaving |
| Cache (#31) | CacheManager | Consistent eviction state across consumers |
| Config (many) | AppConfig / Environment | Loaded once from file/env |
| Notification (#18) | NotificationQueue | Single queue of pending sends |
| Job Scheduler (#32) | Scheduler | One scheduler loop managing all timers |
How to Present in an Interview
"If this class is instantiated multiple times, we'd get [inconsistent state / duplicate threads / wasted memory]. I'll make it a singleton. For thread safety, I'll use double-checked locking with a volatile field (Java) or a module-level instance (Python). The class uses a private constructor so no one else can instantiate it."
When NOT to Use Singleton
- Singleton makes unit testing harder (you can't inject a mock). If testability matters, pass instances via dependency injection instead.
- If you need multiple instances later (multi-tenant config), Singleton is wrong.
Implementation Flavors
1. Eager Initialization (Simple, Safe)
Static initialization creates the instance immediately during class loading. It is simple, thread-safe, and perfect if the instance is cheap or always used. To be absolutely resilient against reflection/serialization attacks out of the box, use an **Enum Singleton**.
2. Lazy Initialization (Thread-Safe)
Use when initialization is expensive (reads from disk, connects to DB) and you may not always need it. For senior/production-ready applications, the implementation must prevent **Reflection attacks**, **Serialization bypasses**, and **Cloning bypasses**, while securing thread-safety via **Double-Checked Locking (DCL)** with a volatile field, or the static **Bill Pugh Holder Idiom**.
// โโโ DOUBLE-CHECKED LOCKING SINGLETON (SENIOR PATTERN) โโโโโโโโโโโโโโโโโโโโโโ
// Thread-safe, high-performance, and resilient against typical bypass vectors:
// 1. Reflection Attacks (guards in constructor)
// 2. Serialization Attacks (defines readResolve())
// 3. Cloning Attacks (overrides clone())
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import java.io.Serializable;
final class DatabaseConnectionPool implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
// volatile is CRITICAL: prevents instruction reordering.
// Without volatile, a thread may observe a non-null, uninitialized reference:
// Memory Allocated -> Assigned to Reference -> Constructor Executes (Reordered!)
private static volatile DatabaseConnectionPool instance;
private DatabaseConnectionPool() {
// Reflection defense: prevent instantiation via Constructor.newInstance()
if (instance != null) {
throw new IllegalStateException("Instance already initialized. Use getInstance().");
}
System.out.println(" [DatabaseConnectionPool] Expensive resource initialization...");
}
public static DatabaseConnectionPool getInstance() {
DatabaseConnectionPool localRef = instance; // Use local variable for 25% performance boost
if (localRef == null) {
synchronized (DatabaseConnectionPool.class) {
localRef = instance;
if (localRef == null) {
instance = localRef = new DatabaseConnectionPool();
}
}
}
return localRef;
}
// Serialization defense: returns the existing instance during deserialization
protected Object readResolve() {
return getInstance();
}
// Cloning defense: prevent cloning
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Singleton cloning is prohibited.");
}
}
public class Main {
public static void main(String[] args) {
DatabaseConnectionPool pool1 = DatabaseConnectionPool.getInstance();
DatabaseConnectionPool pool2 = DatabaseConnectionPool.getInstance();
System.out.println(" [Main] Same instance? " + (pool1 == pool2)); // Should output: true
}
}Review
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Discussion
Share your thoughts, ask questions, or help others.