Design Principle
Singleton Pattern
Learn the Singleton pattern: ensure a class has only one instance with global access. Understand when to use it, common pitfalls, and better alternatives.
Singleton Pattern
Why This Matters
Think of the Singleton pattern like having one main control panel for a building. You don't want multiple control panels—that would be confusing and could cause conflicts. You want one central control panel that everyone uses. The Singleton pattern does the same for code—it ensures only one instance of a class exists, providing a single point of control.
This matters because some resources should only have one instance: database connections (you want one connection pool, not many), loggers (you want one logger that writes to one file), configuration managers (you want one source of truth). The Singleton pattern enforces this, preventing accidental creation of multiple instances.
In interviews, when someone asks "How would you ensure only one instance of a class exists?", they're testing whether you understand the Singleton pattern. Do you know when to use it? Do you understand the pitfalls? Most engineers don't. They use Singletons everywhere and wonder why the code is hard to test.
What Engineers Usually Get Wrong
Most engineers think "Singleton is always good for shared resources." But Singletons create global state, which makes code hard to test. You can't easily replace a Singleton with a mock in tests. Also, Singletons can hide dependencies—code that uses a Singleton depends on it, but that dependency isn't explicit. This makes code harder to understand and maintain.
Engineers also don't understand thread safety. A naive Singleton implementation isn't thread-safe. If multiple threads try to create the instance simultaneously, you might get multiple instances. Always use thread-safe initialization (e.g., double-checked locking, or rely on class loading guarantees).
How This Breaks Systems in the Real World
A service was using a Singleton for database connections. The Singleton created a connection pool. Under normal load, this worked. But during high traffic, the connection pool became exhausted. The Singleton couldn't create more connections because it was designed to have only one pool. The service became unavailable. The fix? Don't use Singleton for resources that need to scale. Use dependency injection instead, allowing multiple instances when needed.
Another story: A service was using a Singleton logger. The logger wrote to a file. When the service ran in multiple processes, each process had its own Singleton instance, but they all tried to write to the same file. File writes conflicted, causing corruption. The fix? Use a Singleton per process, or use a centralized logging service that handles concurrent writes.
What is the Singleton Pattern?
Singleton Pattern guarantees:
- Single instance: Only one instance of the class exists
- Global access: Provides a way to access that instance
- Lazy initialization: Instance created only when needed
- Controlled access: Prevents creation of multiple instances
Use cases:
- Database connections
- Logger instances
- Configuration managers
- Cache managers
- Thread pools
Implementation
Basic Singleton (Not Thread-Safe)
1class Singleton {2 private static instance: Singleton;34 // Private constructor prevents instantiation5 private constructor() {}67 // Public method to get instance8 public static getInstance(): Singleton {9 if (!Singleton.instance) {10 Singleton.instance = new Singleton();11 }12 return Singleton.instance;13 }14}1516// Usage17const instance1 = Singleton.getInstance();
Thread-Safe Singleton (Eager Initialization)
1class Singleton {2 // Eager initialization - created at class load time3 private static instance: Singleton = new Singleton();45 private constructor() {}67 public static getInstance(): Singleton {8 return Singleton.instance;9 }10}
Thread-Safe Singleton (Lazy Initialization with Double-Check Locking)
1class Singleton {2 private static instance: Singleton;3 private static lock: object = new Object();45 private constructor() {}67 public static getInstance(): Singleton {8 if (!Singleton.instance) {9 synchronized (Singleton.lock) {10 if (!Singleton.instance) {11 Singleton.instance = new Singleton();12 }
Examples
Database Connection Singleton
1class DatabaseConnection {2 private static instance: DatabaseConnection;3 private connection: any;45 private constructor() {6 // Initialize database connection7 this.connection = this.initializeConnection();8 }910 public static getInstance(): DatabaseConnection {11 if (!DatabaseConnection.instance) {12 DatabaseConnection.instance = new DatabaseConnection();13 }14 return DatabaseConnection.instance;
Logger Singleton
1class Logger {2 private static instance: Logger;3 private logs: string[] = [];45 private constructor() {}67 public static getInstance(): Logger {8 if (!Logger.instance) {9 Logger.instance = new Logger();10 }11 return Logger.instance;12 }1314 public log(message: string):
Common Pitfalls
- Thread safety: Not thread-safe in multi-threaded environments. Fix: Use double-check locking or eager initialization
- Testing difficulty: Hard to test due to global state. Fix: Use dependency injection, make singleton testable
- Hidden dependencies: Creates hidden dependencies. Fix: Use dependency injection instead
- Global state: Can lead to global state issues. Fix: Consider alternatives like dependency injection
- Lazy initialization issues: Can cause issues in multi-threaded environments. Fix: Use proper synchronization
Interview Questions
Beginner
Q: What is the Singleton pattern and when would you use it?
A:
Singleton Pattern ensures a class has only one instance and provides global access to it.
Key characteristics:
- Single instance: Only one instance exists
- Private constructor: Prevents direct instantiation
- Static method: Provides access to the instance
- Global access: Can be accessed from anywhere
When to use:
- Database connections: Single connection pool
- Loggers: Single logging instance
- Configuration: Single configuration manager
- Cache: Single cache instance
- Thread pools: Single thread pool manager
Example:
1class Singleton {2 private static instance: Singleton;34 private constructor() {}56 public static getInstance(): Singleton {7 if (!Singleton.instance) {8 Singleton.instance = new Singleton();9 }10 return Singleton.instance;11 }12}
Benefits:
- Controlled access: Only one instance
- Global access: Accessible from anywhere
- Lazy initialization: Created when needed
Intermediate
Q: Explain thread-safety issues with the Singleton pattern. How do you implement a thread-safe Singleton?
A:
Thread-Safety Issues:
Problem:
1// Not thread-safe2public static getInstance(): Singleton {3 if (!Singleton.instance) { // Thread 1 checks4 // Thread 2 might also check here5 Singleton.instance = new Singleton(); // Both create instances6 }7 return Singleton.instance;8}
Multiple threads can create multiple instances.
Solutions:
1. Eager Initialization:
1// Created at class load time (thread-safe)2private static instance: Singleton = new Singleton();34public static getInstance(): Singleton {5 return Singleton.instance;6}
2. Double-Check Locking:
1private static instance: Singleton;2private static lock: object = new Object();34public static getInstance(): Singleton {5 if (!Singleton.instance) {6 synchronized (lock) {7 if (!Singleton.instance) {8 instance = new Singleton();9 }10 }11 }12 return Singleton.instance;13}
3. Enum Singleton (Java):
1// TypeScript doesn't have enums like Java, but we can use const object2const Singleton = {3 INSTANCE: {4 doSomething() {5 // ...6 }7 }8} as const;
Trade-offs:
- Eager: Simple, but instance created even if not used
- Double-check: Lazy, but more complex
- Enum: Thread-safe, but less flexible
Senior
Q: Design a thread-safe Singleton pattern for a high-concurrency application. How do you handle lazy initialization, prevent multiple instances, and ensure performance?
A:
1class HighPerformanceSingleton {2 private static instance: HighPerformanceSingleton;3 private static readonly lock: object = new Object();4 private static initialized: boolean = false;56 private constructor() {7 // Expensive initialization8 this.initialize();9 }1011 // Thread-safe lazy initialization12 public static getInstance(): HighPerformanceSingleton {13 // First check (no lock) - performance optimization14 if (!HighPerformanceSingleton.initialized
Features:
- Double-check locking: Prevents multiple instances
- Volatile: Ensures visibility across threads
- Lazy initialization: Created only when needed
- Performance: First check without lock for speed
Failure Stories You'll Recognize
The Exhausted Connection Pool: A service was using a Singleton for database connections. The Singleton created a connection pool. Under normal load, this worked. But during high traffic, the connection pool became exhausted. The Singleton couldn't create more connections because it was designed to have only one pool. The service became unavailable. The fix? Don't use Singleton for resources that need to scale. Use dependency injection instead, allowing multiple instances when needed.
The File Write Conflicts: A service was using a Singleton logger. The logger wrote to a file. When the service ran in multiple processes, each process had its own Singleton instance, but they all tried to write to the same file. File writes conflicted, causing corruption. The fix? Use a Singleton per process, or use a centralized logging service that handles concurrent writes.
The Hard-to-Test Code: A service was using Singletons everywhere—for database connections, loggers, configuration, etc. When the team tried to write tests, they couldn't mock these Singletons. Tests were slow and brittle. The fix? Use dependency injection. Pass dependencies as parameters instead of accessing Singletons. This makes code testable and flexible.
What Interviewers Are Really Testing
They want to hear you talk about Singleton as a tool with trade-offs, not a rule to always follow. Junior engineers say "use Singleton for shared resources." Senior engineers say "Singleton ensures one instance, but creates global state that's hard to test. Use it sparingly—for truly global resources like loggers. Consider dependency injection instead. Always make Singletons thread-safe."
When they ask "How would you ensure only one instance exists?", they're testing:
-
Do you understand when Singleton is appropriate?
-
Do you know the pitfalls (testing, thread safety)?
-
Can you design alternatives (dependency injection)?
-
Singleton pattern: Ensures only one instance exists
-
Private constructor: Prevents direct instantiation
-
Static method: Provides access to instance
-
Thread safety: Use double-check locking or eager initialization
-
Use cases: Database connections, loggers, configuration managers
-
Pitfalls: Testing difficulty, hidden dependencies, global state
-
Best practices: Consider dependency injection, make testable, use proper synchronization
How InterviewCrafted Will Teach This
We'll teach this through real code problems, not abstract patterns. Instead of memorizing "Singleton ensures one instance," you'll learn through scenarios like "why is our code hard to test?"
You'll see how Singleton affects testability, maintainability, and scalability. When an interviewer asks "how would you ensure only one instance exists?", you'll think about Singleton, dependency injection, and trade-offs—not just "use Singleton."
- Factory Pattern - Factories can manage singleton instances. Understanding factories helps understand singleton creation patterns.
- SOLID Principles - Singleton can violate Dependency Inversion Principle. Understanding SOLID helps understand singleton trade-offs.
- Separation of Concerns - Singleton can create hidden dependencies. Understanding separation of concerns helps understand singleton pitfalls.
- DRY, KISS & YAGNI - Singleton can add complexity. Understanding DRY/KISS/YAGNI helps decide when singleton is appropriate.
- Composition Over Inheritance - Dependency injection (alternative to singleton) uses composition. Understanding composition helps understand singleton alternatives.
Key Takeaways
Singleton pattern: Ensures only one instance exists
Private constructor: Prevents direct instantiation
Static method: Provides access to instance
Thread safety: Use double-check locking or eager initialization
Use cases: Database connections, loggers, configuration managers
Pitfalls: Testing difficulty, hidden dependencies, global state
Best practices: Consider dependency injection, make testable, use proper synchronization
Related Topics
Factory Pattern
Factories can manage singleton instances. Understanding factories helps understand singleton creation patterns.
SOLID Principles
Singleton can violate Dependency Inversion Principle. Understanding SOLID helps understand singleton trade-offs.
Separation of Concerns
Singleton can create hidden dependencies. Understanding separation of concerns helps understand singleton pitfalls.
DRY, KISS & YAGNI
Singleton can add complexity. Understanding DRY/KISS/YAGNI helps decide when singleton is appropriate.
Composition Over Inheritance
Dependency injection (alternative to singleton) uses composition. Understanding composition helps understand singleton alternatives.
Keep exploring
Principles work best in chorus. Pair this lesson with another concept and observe how your architecture conversations change.