Singleton Pattern: Ensure One Instance

Updated 2026-03-11

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.

Prerequisites: Understanding of classes and objects, static methods and class variables, basic understanding of constructors and instantiation. Familiarity with the concept of design patterns is helpful but not required.

After this topic: Implement thread-safe Singleton patterns in Python and understand their variations. Identify when to use (and when to avoid) Singletons in real-world applications. Explain the trade-offs between different Singleton implementations during technical interviews.

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:

  1. Resource Management: Database connections, file handles, or thread pools should be shared, not duplicated
  2. Configuration: Application settings should be consistent across all components
  3. Logging: A single logger ensures all log entries go to the same destination
  4. Caching: One cache instance prevents memory duplication

Core Mechanism

The Singleton pattern works by:

  1. Making the constructor private (or controlling instantiation)
  2. Storing the single instance as a class variable
  3. Providing a static method to access that instance
  4. 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 logging module 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.