Singleton Pattern
Ensure a class has only one instance and provide a global point of access to it. One of the most commonly asked patterns in interviews.
1What is Singleton?
A country has only ONE government at a time. You cannot create multiple governments. Everyone who needs to interact with the government uses the same instance.
Your application should have only ONE connection pool to manage database connections efficiently. Creating multiple pools would waste resources and cause conflicts.
- Database connection pool
- Logger instance
- Configuration manager
- Cache manager
- Thread pool
- Just for global access (use DI instead)
- When you need multiple instances later
- When testability is critical
- Stateless utility classes
2Implementation Approaches
There are several ways to implement Singleton, each with trade-offs:
- +Thread-safe by default
- +Simple implementation
- -Instance created even if never used
- -No exception handling in initialization
- +Instance created only when needed
- +Saves memory if never used
- -Not thread-safe without synchronization
- +Thread-safe
- +Lazy initialization
- +Good performance after first call
- -Complex implementation
- -Requires volatile keyword
- +Thread-safe
- +Lazy
- +No synchronization overhead
- -Java-specific idiom
3Implementation
// ========== Method 1: Eager Initialization ==========
public class EagerSingleton {
// Instance created at class loading time
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
// Private constructor prevents instantiation
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
// ========== Method 2: Double-Checked Locking ==========
public class DCLSingleton {
// volatile prevents instruction reordering
private static volatile DCLSingleton instance;
private DCLSingleton() {}
public static DCLSingleton getInstance() {
if (instance == null) { // First check (no locking)
synchronized (DCLSingleton.class) {
if (instance == null) { // Second check (with lock)
instance = new DCLSingleton();
}
}
}
return instance;
}
}
// ========== Method 3: Bill Pugh (Recommended for Java) ==========
public class BillPughSingleton {
private BillPughSingleton() {}
// Static inner class - loaded only when getInstance() is called
private static class SingletonHelper {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
// ========== Method 4: Enum (Most Robust) ==========
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("Singleton using Enum");
}
}
// Usage
public class Main {
public static void main(String[] args) {
BillPughSingleton s1 = BillPughSingleton.getInstance();
BillPughSingleton s2 = BillPughSingleton.getInstance();
System.out.println(s1 == s2); // true - same instance
}
}Singleton created true Singleton doing something
4Thread Safety Deep Dive
Understanding why thread safety matters in Singleton:
The Race Condition Problem
Thread 1 Thread 2
-------- --------
if (instance == null)
if (instance == null)
instance = new Singleton()
instance = new Singleton() // OOPS!
return instance return instance // Different instance!Without proper synchronization, two threads can both see instance == null and create separate instances, breaking the singleton guarantee.
- Java: Use Bill Pugh (static inner class) or Enum singleton
- Python: Use module-level instance or metaclass approach
- C++11+: Use Meyers' Singleton (static local variable)
- All: Consider dependency injection instead of raw singleton
5Singleton Drawbacks
Singleton is often considered an anti-pattern. Understand why:
6Interview Follow-up Questions
Interview Follow-up Questions
Common follow-up questions interviewers ask