Singleton Pattern: Ensure One Instance
TL;DR
The Singleton pattern ensures a class has only one instance throughout your application and provides a global point of access to it. It’s useful for managing shared resources like database connections, configuration settings, or logging systems where multiple instances would cause problems or waste resources.
Core Concept
What is the Singleton Pattern?
The Singleton pattern is a creational design pattern that restricts a class to having exactly one instance. Instead of creating new objects every time you need one, the Singleton ensures you always get the same instance back.
Why Use Singletons?
Singletons solve specific problems:
- Resource Management: Database connections, file handles, or thread pools should be shared, not duplicated
- Configuration: Application settings should be consistent across all components
- Logging: A single logger ensures all log entries go to the same destination
- Caching: One cache instance prevents memory duplication
Core Mechanism
The Singleton pattern works by:
- Making the constructor private (or controlling instantiation)
- Storing the single instance as a class variable
- Providing a static method to access that instance
- Creating the instance only on first access (lazy initialization)
Key Characteristics
Single Instance: Only one object exists in memory, no matter how many times you request it.
Global Access: The instance is accessible from anywhere in your application through a well-known access point.
Lazy or Eager Initialization: The instance can be created when first needed (lazy) or when the class loads (eager).
When NOT to Use Singletons
Singletons are often considered an anti-pattern because they:
- Create hidden dependencies (global state)
- Make unit testing difficult
- Violate the Single Responsibility Principle
- Can cause issues in multi-threaded environments
Modern alternatives include dependency injection and service locators.
Visual Guide
Singleton Pattern Structure
classDiagram
class Singleton {
-static instance: Singleton
-Singleton()
+static getInstance(): Singleton
+businessMethod()
}
note for Singleton "Private constructor prevents\ndirect instantiation"
Client1 --> Singleton : getInstance()
Client2 --> Singleton : getInstance()
note for Client1 "Both clients get\nthe same instance"
The Singleton class controls its own instantiation through a private constructor and static getInstance method. All clients receive the same instance.
Singleton Instance Creation Flow
sequenceDiagram
participant Client1
participant Singleton
participant Client2
Client1->>Singleton: getInstance()
Note over Singleton: instance == None?<br/>Create new instance
Singleton-->>Client1: return instance
Client2->>Singleton: getInstance()
Note over Singleton: instance exists<br/>Return existing
Singleton-->>Client2: return instance
Note over Client1,Client2: Both hold reference<br/>to same object
First call creates the instance; subsequent calls return the existing instance. This ensures only one object exists.
Examples
Example 1: Basic Singleton Implementation
class DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
print("Creating new database connection")
cls._instance = super().__new__(cls)
cls._instance.connection_string = "db://localhost:5432"
return cls._instance
def query(self, sql):
return f"Executing: {sql} on {self.connection_string}"
# Usage
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(f"db1 is db2: {db1 is db2}") # Output: True
print(f"db1 id: {id(db1)}") # Output: 140234567890 (example)
print(f"db2 id: {id(db2)}") # Output: 140234567890 (same!)
print(db1.query("SELECT * FROM users"))
# Output: Executing: SELECT * FROM users on db://localhost:5432
Expected Output:
Creating new database connection
db1 is db2: True
db1 id: 140234567890
db2 id: 140234567890
Executing: SELECT * FROM users on db://localhost:5432
How it works: Python’s __new__ method controls object creation. We check if _instance exists; if not, we create it. All subsequent calls return the same instance.
Try it yourself: Add a counter that increments each time query() is called. Verify that both db1 and db2 share the same counter value.
Example 2: Thread-Safe Singleton with Lock
import threading
class Logger:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock: # Thread-safe check
if cls._instance is None: # Double-checked locking
print(f"Thread {threading.current_thread().name}: Creating logger")
cls._instance = super().__new__(cls)
cls._instance.logs = []
return cls._instance
def log(self, message):
self.logs.append(f"[{threading.current_thread().name}] {message}")
return f"Logged: {message}"
# Test with multiple threads
def worker(n):
logger = Logger()
logger.log(f"Worker {n} started")
threads = [threading.Thread(target=worker, args=(i,), name=f"Thread-{i}")
for i in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
logger = Logger()
print(f"Total logs: {len(logger.logs)}") # Output: Total logs: 5
for log in logger.logs:
print(log)
Expected Output:
Thread Thread-0: Creating logger
Total logs: 5
[Thread-0] Worker 0 started
[Thread-1] Worker 1 started
[Thread-2] Worker 2 started
[Thread-3] Worker 3 started
[Thread-4] Worker 4 started
Key points: The _lock ensures only one thread creates the instance. Double-checked locking (checking _instance twice) improves performance by avoiding lock acquisition after initialization.
Try it yourself: Remove the lock and run the code multiple times. You might see multiple “Creating logger” messages, demonstrating the race condition.
Example 3: Decorator-Based Singleton (Pythonic Approach)
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Configuration:
def __init__(self):
print("Loading configuration...")
self.settings = {
'debug': True,
'max_connections': 100
}
def get(self, key):
return self.settings.get(key)
# Usage
config1 = Configuration()
config2 = Configuration()
print(f"config1 is config2: {config1 is config2}") # Output: True
print(f"Debug mode: {config1.get('debug')}") # Output: Debug mode: True
config1.settings['debug'] = False
print(f"Debug via config2: {config2.get('debug')}") # Output: Debug via config2: False
Expected Output:
Loading configuration...
config1 is config2: True
Debug mode: True
Debug via config2: False
Why this is Pythonic: Decorators are idiomatic Python. This approach is cleaner and more reusable than modifying __new__.
Try it yourself: Apply the @singleton decorator to multiple classes and verify each maintains its own single instance.
Example 4: Metaclass-Based Singleton
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Cache(metaclass=SingletonMeta):
def __init__(self):
print("Initializing cache")
self.data = {}
def set(self, key, value):
self.data[key] = value
def get(self, key):
return self.data.get(key)
# Usage
cache1 = Cache()
cache1.set('user_1', {'name': 'Alice'})
cache2 = Cache()
print(cache2.get('user_1')) # Output: {'name': 'Alice'}
print(f"Same instance: {cache1 is cache2}") # Output: Same instance: True
Expected Output:
Initializing cache
{'name': 'Alice'}
Same instance: True
Metaclass approach: Metaclasses control class creation. By overriding __call__, we intercept instance creation at the class level.
Java/C++ Note: In Java, use a private constructor with a static getInstance() method. In C++, use a static local variable in getInstance() for thread-safe initialization (since C++11).
Try it yourself: Create a SessionManager singleton that tracks active user sessions. Add methods to add/remove sessions and verify state is shared across all references.
Common Mistakes
1. Forgetting Thread Safety in Multi-threaded Applications
The Mistake:
class Database:
_instance = None
def __new__(cls):
if cls._instance is None: # Race condition here!
cls._instance = super().__new__(cls)
return cls._instance
Why it’s wrong: Two threads can simultaneously check if cls._instance is None, both see None, and both create instances. You end up with multiple instances.
Fix: Use a lock with double-checked locking as shown in Example 2.
2. Not Handling Subclassing Properly
The Mistake:
class Parent:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
class Child(Parent):
pass
p = Parent()
c = Child()
print(p is c) # Output: True (WRONG! Different classes should have different instances)
Why it’s wrong: Both parent and child share the same _instance variable, so Child() returns a Parent instance.
Fix: Use cls properly and store instances per class:
class Parent:
_instances = {}
def __new__(cls):
if cls not in cls._instances:
cls._instances[cls] = super().__new__(cls)
return cls._instances[cls]
3. Making Singletons Difficult to Test
The Mistake: Creating a Singleton without a way to reset it between tests:
class APIClient:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.request_count = 0
return cls._instance
# In tests, the request_count persists across test cases!
Why it’s wrong: Unit tests should be isolated. A Singleton’s state can leak between tests, causing flaky tests.
Fix: Add a reset method for testing or use dependency injection instead:
class APIClient:
_instance = None
@classmethod
def reset(cls): # For testing only
cls._instance = None
4. Using Singletons for Everything (Anti-pattern)
The Mistake: Making every utility class a Singleton “just in case”:
@singleton
class StringHelper: # Doesn't need to be a Singleton!
def capitalize(self, s):
return s.upper()
Why it’s wrong: Singletons introduce global state and tight coupling. If a class is stateless or doesn’t manage shared resources, it doesn’t need to be a Singleton.
Fix: Use regular classes, static methods, or module-level functions. Reserve Singletons for truly shared resources.
5. Ignoring Serialization Issues
The Mistake:
import pickle
class Config:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
config1 = Config()
serialized = pickle.dumps(config1)
config2 = pickle.loads(serialized)
print(config1 is config2) # Output: False (Singleton broken!)
Why it’s wrong: Pickling creates a new object when unpickling, breaking the Singleton guarantee.
Fix: Implement __reduce__ or __getnewargs__ to return the existing instance:
def __reduce__(self):
return (self.__class__, ())
Interview Tips
What Interviewers Look For
1. Know When NOT to Use It
Interviewers want to see critical thinking. When asked about Singletons, mention the downsides:
- “Singletons can make testing difficult because they introduce global state”
- “Modern applications often use dependency injection instead”
- “I’d consider whether the shared resource truly needs global access”
This shows maturity beyond just implementing the pattern.
2. Discuss Thread Safety Immediately
If you’re asked to implement a Singleton, ask: “Should this be thread-safe?” Then explain:
- Simple implementation (single-threaded)
- Lock-based approach (multi-threaded)
- Double-checked locking optimization
Example answer: “In a multi-threaded environment, I’d use a lock to prevent race conditions during initialization. Double-checked locking avoids the performance overhead of acquiring the lock on every access.”
3. Compare Language Implementations
Be ready to discuss differences:
- Python:
__new__method, decorators, or metaclasses - Java: Private constructor with static
getInstance(), enum-based Singleton (best practice) - C++: Static local variable in
getInstance()(thread-safe since C++11)
Example: “In Java, I’d use an enum for the simplest thread-safe Singleton, or a static inner class for lazy initialization.”
4. Real-World Examples
Have concrete examples ready:
- “Database connection pools use Singletons to manage limited connections”
- “Logging frameworks like Python’s
loggingmodule use Singleton-like patterns” - “Application configuration loaded once at startup”
But also mention: “In microservices, we often avoid Singletons in favor of dependency injection containers like Spring or FastAPI’s dependency system.”
5. Handle the “Alternatives” Question
Interviewers often ask: “What would you use instead of a Singleton?”
Good answers:
- Dependency Injection: “Pass the shared instance as a parameter rather than accessing it globally”
- Monostate Pattern: “Share state without enforcing single instance”
- Service Locator: “Centralized registry for services”
- Module-level variables: “In Python, modules are natural Singletons”
6. Code Review Scenario
Be prepared for: “You see a Singleton in a code review. What do you check?”
Answer checklist:
- Is it thread-safe if needed?
- Can it be tested easily (is there a reset mechanism)?
- Does it truly need to be a Singleton, or is it just convenient?
- Are there hidden dependencies on this global state?
- How does it handle serialization/deserialization?
7. Design Question Integration
Singletons often appear in system design questions:
- “Design a rate limiter” → Singleton for tracking request counts
- “Design a cache system” → Singleton for the cache manager
- “Design a logging system” → Singleton for the logger
Always mention: “I’d use a Singleton for the [component], ensuring thread-safe access with [specific technique], but I’d inject it as a dependency rather than accessing it globally.”
Practice Question: “Implement a thread-safe Singleton in your preferred language and explain how you’d test it.”
Strong answer includes: Implementation with lock, explanation of double-checked locking, mention of testing challenges, and a reset method for test isolation.
Key Takeaways
-
The Singleton pattern ensures only one instance of a class exists, providing global access to shared resources like database connections, configuration, or logging systems.
-
Thread safety is critical in multi-threaded environments—use locks with double-checked locking to prevent race conditions during instance creation while maintaining performance.
-
Singletons are often considered an anti-pattern because they introduce global state, make testing difficult, and create hidden dependencies. Modern alternatives include dependency injection and service locators.
-
Different languages offer different implementation approaches: Python uses
__new__, decorators, or metaclasses; Java uses private constructors or enums; C++ uses static local variables for thread-safe initialization. -
In interviews, demonstrate critical thinking by discussing when NOT to use Singletons, explaining thread-safety considerations, and suggesting alternatives like dependency injection for better testability and maintainability.