Skip to content

Singleton Pattern

Ensure only one instance exists - control access to shared resources!

Singleton Pattern: One Instance to Rule Them All

Section titled “Singleton Pattern: One Instance to Rule Them All”

Now let’s dive into the Singleton Pattern - a creational design pattern that ensures a class has only one instance and provides a global point of access to it.

Imagine a pizza shop. You only need one manager to coordinate everything - you don’t want multiple managers giving conflicting orders! The Singleton Pattern works the same way!

The Singleton Pattern ensures that a class has only one instance and provides a global point of access to that instance. Instead of creating multiple instances, everyone uses the same one.

The Singleton Pattern is useful when:

  1. You need exactly one instance - Multiple instances would cause problems
  2. Shared resource management - Database connections, file systems, caches
  3. Global configuration - Application settings that should be consistent
  4. Logging systems - One logger instance for the entire application
  5. Resource-intensive objects - Objects that are expensive to create (only create once)
  6. State management - When you need a single source of truth

What Happens If We Don’t Use Singleton Pattern?

Section titled “What Happens If We Don’t Use Singleton Pattern?”

Without the Singleton Pattern, you might:

  • Create multiple instances - Wasting memory and resources
  • Inconsistent state - Different parts of code using different instances
  • Resource conflicts - Multiple connections to the same resource
  • Configuration chaos - Different parts reading different configurations
  • Performance issues - Creating expensive objects multiple times
  • Race conditions - Multiple instances competing for the same resource

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Singleton Pattern works in practice - showing how multiple clients get the same instance:

sequenceDiagram
    participant Client1
    participant Client2
    participant Client3
    participant Singleton as PizzaShopManager
    
    Client1->>Singleton: get_instance()
    activate Singleton
    Singleton->>Singleton: Check if instance exists
    Singleton->>Singleton: Create instance (first time)
    Singleton-->>Client1: Returns instance #1
    deactivate Singleton
    
    Client2->>Singleton: get_instance()
    activate Singleton
    Singleton->>Singleton: Check if instance exists
    Singleton-->>Client2: Returns same instance #1
    deactivate Singleton
    
    Client3->>Singleton: get_instance()
    activate Singleton
    Singleton->>Singleton: Check if instance exists
    Singleton-->>Client3: Returns same instance #1
    deactivate Singleton
    
    Note over Client1,Singleton: All clients get<br/>the SAME instance!

You’re building a pizza shop system. You need a manager to coordinate orders, but you only want one manager - having multiple managers would cause chaos! Without Singleton Pattern:

bad_manager.py
# ❌ Without Singleton Pattern - Multiple instances possible!
class PizzaShopManager:
def __init__(self):
self.orders = []
print("Creating a new PizzaShopManager instance!")
def add_order(self, order: str):
self.orders.append(order)
print(f"Order added: {order}. Total orders: {len(self.orders)}")
def get_orders(self):
return self.orders
# Problem: Each part of code creates its own manager!
def process_order():
manager1 = PizzaShopManager() # Creates instance 1
manager1.add_order("Margherita")
return manager1
def track_orders():
manager2 = PizzaShopManager() # Creates instance 2 - Different instance!
manager2.add_order("Pepperoni")
return manager2
def generate_report():
manager3 = PizzaShopManager() # Creates instance 3 - Yet another instance!
return manager3.get_orders()
# Usage - Problem!
process_order() # Manager 1 has 1 order
track_orders() # Manager 2 has 1 order (different instance!)
report = generate_report() # Manager 3 has 0 orders (yet another instance!)
# Problems:
# - Three different managers with different states
# - Orders are scattered across instances
# - No single source of truth
# - Memory waste (3 instances instead of 1)

Problems:

  • Multiple instances with different states
  • No single source of truth
  • Memory waste
  • Inconsistent data across the application
classDiagram
    class PizzaShopManager {
        -_instance: PizzaShopManager
        -orders: List
        +__new__() PizzaShopManager
        +add_order(order) void
        +get_orders() List
    }
    class Client1 {
        +get_manager() PizzaShopManager
    }
    class Client2 {
        +get_manager() PizzaShopManager
    }
    class Client3 {
        +get_manager() PizzaShopManager
    }
    
    Client1 --> PizzaShopManager : gets instance
    Client2 --> PizzaShopManager : gets instance
    Client3 --> PizzaShopManager : gets instance
    
    note for PizzaShopManager "Only ONE instance<br/>exists globally"
    note for PizzaShopManager "All clients get<br/>the same instance"
singleton_manager.py
class PizzaShopManager:
"""Singleton class - only one instance can exist"""
_instance = None # Class variable to store the single instance
def __new__(cls):
"""Override __new__ to control instance creation"""
if cls._instance is None:
print("Creating the one and only PizzaShopManager instance!")
cls._instance = super(PizzaShopManager, cls).__new__(cls)
cls._instance.orders = [] # Initialize instance variables
else:
print("PizzaShopManager instance already exists - returning existing one!")
return cls._instance
def add_order(self, order: str):
"""Add an order"""
self.orders.append(order)
print(f"Order added: {order}. Total orders: {len(self.orders)}")
def get_orders(self):
"""Get all orders"""
return self.orders.copy() # Return a copy to prevent external modification
# Usage - All get the same instance!
def process_order():
manager1 = PizzaShopManager() # Creates the instance
manager1.add_order("Margherita")
return manager1
def track_orders():
manager2 = PizzaShopManager() # Returns existing instance!
manager2.add_order("Pepperoni")
return manager2
def generate_report():
manager3 = PizzaShopManager() # Returns existing instance!
return manager3.get_orders()
# Usage - All use the same instance!
process_order() # Manager has 1 order
track_orders() # Same manager now has 2 orders!
report = generate_report() # Same manager - shows 2 orders!
print(f"All managers are the same: {process_order() is track_orders() is generate_report()}") # True!

Real-World Software Example: Database Connection Manager

Section titled “Real-World Software Example: Database Connection Manager”

Now let’s see a realistic software example - a database connection manager that should only have one instance to manage connection pooling efficiently.

You’re building an application that needs database access. Creating multiple connection managers would waste resources and cause connection pool conflicts. Without Singleton Pattern:

bad_db_manager.py
# ❌ Without Singleton Pattern - Multiple connection managers!
class DatabaseConnectionManager:
def __init__(self):
self.connection_pool = []
self.max_connections = 10
print(f"Creating DatabaseConnectionManager with {self.max_connections} max connections")
# Expensive initialization - connecting to database, setting up pool, etc.
def get_connection(self):
if len(self.connection_pool) < self.max_connections:
conn = f"Connection-{len(self.connection_pool) + 1}"
self.connection_pool.append(conn)
return conn
raise Exception("Connection pool exhausted!")
def release_connection(self, conn):
if conn in self.connection_pool:
self.connection_pool.remove(conn)
# Problem: Each module creates its own manager!
class UserService:
def __init__(self):
self.db_manager = DatabaseConnectionManager() # Creates instance 1
def get_user(self, user_id):
conn = self.db_manager.get_connection()
# Use connection...
self.db_manager.release_connection(conn)
class OrderService:
def __init__(self):
self.db_manager = DatabaseConnectionManager() # Creates instance 2 - Different pool!
def create_order(self, order_data):
conn = self.db_manager.get_connection()
# Use connection...
self.db_manager.release_connection(conn)
class ProductService:
def __init__(self):
self.db_manager = DatabaseConnectionManager() # Creates instance 3 - Yet another pool!
def get_product(self, product_id):
conn = self.db_manager.get_connection()
# Use connection...
self.db_manager.release_connection(conn)
# Problems:
# - Three separate connection pools (30 total connections instead of 10!)
# - Resource waste
# - No shared connection pool
# - Expensive initialization happens 3 times

Problems:

  • Multiple connection pools wasting resources
  • Expensive initialization happens multiple times
  • No shared connection management
  • Potential connection pool exhaustion
singleton_db_manager.py
import threading
class DatabaseConnectionManager:
"""Singleton Database Connection Manager"""
_instance = None
_lock = threading.Lock() # For thread safety
def __new__(cls):
if cls._instance is None:
with cls._lock:
# Double-check locking pattern
if cls._instance is None:
print("Creating the one and only DatabaseConnectionManager!")
cls._instance = super(DatabaseConnectionManager, cls).__new__(cls)
cls._instance.connection_pool = []
cls._instance.max_connections = 10
# Expensive initialization happens only once!
print(f"Initialized connection pool with {cls._instance.max_connections} max connections")
return cls._instance
def get_connection(self):
"""Get a connection from the pool"""
if len(self.connection_pool) < self.max_connections:
conn = f"Connection-{len(self.connection_pool) + 1}"
self.connection_pool.append(conn)
print(f"Got connection: {conn}. Pool size: {len(self.connection_pool)}/{self.max_connections}")
return conn
raise Exception("Connection pool exhausted!")
def release_connection(self, conn):
"""Release a connection back to the pool"""
if conn in self.connection_pool:
self.connection_pool.remove(conn)
print(f"Released connection: {conn}. Pool size: {len(self.connection_pool)}/{self.max_connections}")
# All services use the same instance!
class UserService:
def __init__(self):
self.db_manager = DatabaseConnectionManager() # Gets the singleton instance
def get_user(self, user_id):
conn = self.db_manager.get_connection()
print(f"UserService: Using {conn} to get user {user_id}")
self.db_manager.release_connection(conn)
class OrderService:
def __init__(self):
self.db_manager = DatabaseConnectionManager() # Gets the same singleton instance!
def create_order(self, order_data):
conn = self.db_manager.get_connection()
print(f"OrderService: Using {conn} to create order")
self.db_manager.release_connection(conn)
class ProductService:
def __init__(self):
self.db_manager = DatabaseConnectionManager() # Gets the same singleton instance!
def get_product(self, product_id):
conn = self.db_manager.get_connection()
print(f"ProductService: Using {conn} to get product {product_id}")
self.db_manager.release_connection(conn)
# Usage - All services share the same connection pool!
user_service = UserService()
order_service = OrderService()
product_service = ProductService()
# All use the same DatabaseConnectionManager instance!
print(f"Same instance: {user_service.db_manager is order_service.db_manager is product_service.db_manager}") # True!
user_service.get_user(1)
order_service.create_order({"item": "Pizza"})
product_service.get_product(1)

There are several ways to implement the Singleton Pattern:

1. Eager Initialization (Simple Singleton)

Section titled “1. Eager Initialization (Simple Singleton)”

The instance is created when the class is loaded:

eager_singleton.py
class EagerSingleton:
"""Eager initialization - instance created at class load time"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(EagerSingleton, cls).__new__(cls)
return cls._instance
# Instance is created when class is first accessed

Pros: Simple, thread-safe
Cons: Instance created even if never used

The instance is created only when first requested:

lazy_singleton.py
class LazySingleton:
"""Lazy initialization - instance created only when needed"""
_instance = None
def __new__(cls):
if cls._instance is None:
print("Creating instance for the first time!")
cls._instance = super(LazySingleton, cls).__new__(cls)
return cls._instance
# Instance is created only when first accessed

Pros: Instance created only when needed
Cons: Not thread-safe (can create multiple instances in multi-threaded environment)

3. Thread-Safe Singleton (Double-Check Locking)

Section titled “3. Thread-Safe Singleton (Double-Check Locking)”

Thread-safe version using double-check locking:

thread_safe_singleton.py
import threading
class ThreadSafeSingleton:
"""Thread-safe singleton using double-check locking"""
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
# Double-check after acquiring lock
if cls._instance is None:
cls._instance = super(ThreadSafeSingleton, cls).__new__(cls)
return cls._instance

Pros: Thread-safe, lazy initialization
Cons: Slightly more complex

4. Singleton with Module Pattern (Python-specific)

Section titled “4. Singleton with Module Pattern (Python-specific)”

In Python, modules are naturally singletons:

module_singleton.py
# singleton_module.py
class _Singleton:
def __init__(self):
self.value = None
# Module-level instance
_instance = _Singleton()
def get_instance():
return _instance
# Usage: from singleton_module import get_instance
# This is the most Pythonic way!

Pros: Most Pythonic, simple
Cons: Python-specific


Use Singleton Pattern when:

Exactly one instance needed - Multiple instances would cause problems
Global access required - Need to access from anywhere in the application
Expensive resource - Object is expensive to create (database connections, file handles)
Shared state - Need a single source of truth
Configuration management - Application-wide settings
Logging systems - One logger for the entire application
Caching - Single cache instance
Thread pools - Single thread pool manager

Don’t use Singleton Pattern when:

Multiple instances needed - If you might need multiple instances, don’t use Singleton
Testing difficulties - Singletons can make unit testing harder
Hidden dependencies - Global state can hide dependencies
Concurrency issues - Can cause problems in distributed systems
Violates Single Responsibility - Can become a “god object”
Tight coupling - Creates global coupling


bad_singleton.py
# ❌ Bad: Constructor not private - can create multiple instances!
class BadSingleton:
_instance = None
def __init__(self): # Public constructor!
if BadSingleton._instance is None:
BadSingleton._instance = self
else:
raise Exception("Singleton already exists!")
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls() # Can still call cls()!
return cls._instance
# Problem: Can still create instances directly!
obj1 = BadSingleton() # Works!
obj2 = BadSingleton() # Raises exception, but obj1 and obj2 are different!

Mistake 2: Not Thread-Safe in Multi-Threaded Environment

Section titled “Mistake 2: Not Thread-Safe in Multi-Threaded Environment”
not_thread_safe.py
# ❌ Bad: Not thread-safe - can create multiple instances!
class NotThreadSafeSingleton:
_instance = None
def __new__(cls):
if cls._instance is None:
# Race condition: Multiple threads can pass this check!
cls._instance = super(NotThreadSafeSingleton, cls).__new__(cls)
return cls._instance
# In multi-threaded environment, multiple instances can be created!

Mistake 3: Serializable Singleton Without readResolve

Section titled “Mistake 3: Serializable Singleton Without readResolve”
SerializableSingleton.java
import java.io.Serializable;
// ❌ Bad: Serializable singleton without readResolve
public class BadSerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static BadSerializableSingleton instance = new BadSerializableSingleton();
private BadSerializableSingleton() {}
public static BadSerializableSingleton getInstance() {
return instance;
}
// Problem: Deserialization creates a new instance!
// Need readResolve() method to return the singleton instance
}
// ✅ Good: With readResolve
public class GoodSerializableSingleton implements Serializable {
private static final long serialVersionUID = 1L;
private static GoodSerializableSingleton instance = new GoodSerializableSingleton();
private GoodSerializableSingleton() {}
public static GoodSerializableSingleton getInstance() {
return instance;
}
// Prevents deserialization from creating a new instance
protected Object readResolve() {
return getInstance();
}
}
overuse_singleton.py
# ❌ Bad: Using Singleton for everything!
class UserSingleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(UserSingleton, cls).__new__(cls)
return cls._instance
def __init__(self):
self.name = None
self.email = None
# Problem: What if you need multiple users? Singleton prevents that!
# ✅ Better: Use regular class
class User:
def __init__(self, name, email):
self.name = name
self.email = email

  1. Controlled Access - Only one instance exists, controlled access point
  2. Resource Efficiency - Expensive objects created only once
  3. Global State - Single source of truth for shared state
  4. Memory Efficient - No memory waste from multiple instances
  5. Lazy Initialization - Can defer expensive initialization until needed
  6. Namespace - Provides a namespace for global variables

Singleton Pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance.

  • Exactly one instance - Multiple instances would cause problems
  • Resource efficiency - Expensive objects created only once
  • Global access - Easy to access from anywhere
  • Shared state - Single source of truth
  • Memory efficient - No waste from multiple instances
  1. Private constructor - Prevents external instantiation
  2. Static instance variable - Stores the single instance
  3. Static getter method - Returns the instance (creates if needed)
  4. Controlled creation - Only one instance can exist
Client → getInstance() → Singleton Instance
  • Singleton Class - Class with private constructor
  • Static Instance - The single instance variable
  • Getter Method - Method to get the instance
  • Client - Code that uses the singleton
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
# Usage
obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2) # True - same instance!

✅ Exactly one instance needed
✅ Expensive resource creation
✅ Global access required
✅ Shared state management
✅ Configuration management
✅ Logging systems

❌ Multiple instances might be needed
❌ Testing difficulties are a concern
❌ Hidden dependencies are problematic
❌ Distributed systems
❌ Violates Single Responsibility

  • Singleton Pattern = Only one instance exists
  • Private Constructor = Prevents external creation
  • Static Getter = Controlled access point
  • Benefit = Resource efficiency and global access
  • Caution = Use judiciously, can make testing harder
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
@classmethod
def get_instance(cls):
return cls()
# Usage
instance = Singleton.get_instance()
  • Singleton Pattern ensures only one instance exists
  • It provides controlled access to that instance
  • Use it for expensive resources or shared state
  • Don’t overuse it - can make code harder to test
  • Consider alternatives like dependency injection

What to say:

“Singleton Pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. It’s useful for managing shared resources like database connections, loggers, or configuration managers.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of when to use it
  • Indicates you can explain concepts clearly

Must mention:

  • Exactly one instance needed - Multiple instances would cause problems
  • Expensive resource - Database connections, file handles
  • Global access - Need to access from anywhere
  • Shared state - Single source of truth
  • Configuration - Application-wide settings

Example scenario to give:

“I’d use Singleton Pattern for a database connection manager. Creating multiple connection managers would waste resources and cause connection pool conflicts. With Singleton, all parts of the application share the same connection pool.”

Must explain:

  1. Private Constructor - Prevents external instantiation
  2. Static Instance Variable - Stores the single instance
  3. Static Getter Method - Returns the instance (creates if needed)
  4. Controlled Creation - Ensures only one instance exists

Visual explanation:

Client → getInstance() → Singleton Instance (created once, reused always)

Must discuss:

  • Problem: Multiple threads can create multiple instances
  • Solution: Use synchronization (locks, synchronized blocks)
  • Double-Check Locking: Check twice, lock once

Example to give:

“In a multi-threaded environment, multiple threads might check if the instance is null at the same time and both create instances. To prevent this, I use double-check locking - check if instance is null, acquire lock, check again, then create.”

Benefits to mention:

  • Resource Efficiency - Expensive objects created only once
  • Global Access - Easy to access from anywhere
  • Controlled Access - Single point of control
  • Memory Efficient - No waste from multiple instances

Trade-offs to acknowledge:

  • Testing Difficulties - Hard to mock and test
  • Hidden Dependencies - Global state hides dependencies
  • Tight Coupling - Creates global coupling
  • Concurrency Issues - Can cause problems in distributed systems

Q: “What’s the difference between Singleton and Static Class?”

A:

“A Singleton is an instance of a class that can be created, while a static class cannot be instantiated. Singleton can implement interfaces and be passed as parameters, while static classes cannot. Singleton allows lazy initialization, while static classes are initialized when the class is loaded.”

Q: “How do you make Singleton thread-safe?”

A:

“I use double-check locking - first check if instance is null without locking (for performance), then acquire a lock, check again (to prevent race conditions), and create the instance if still null. Alternatively, I can use eager initialization or synchronized methods, but double-check locking provides the best balance of thread safety and performance.”

Q: “What are the problems with Singleton Pattern?”

A:

“Singleton Pattern can make unit testing difficult because you can’t easily mock the singleton. It also creates hidden dependencies and tight coupling. In distributed systems, each process has its own singleton instance, which can cause inconsistencies. Additionally, it violates the Single Responsibility Principle if the singleton does too much.”

Key implementation points:

  1. Private Constructor - Prevents external instantiation

    def __new__(cls):
    if cls._instance is None:
    cls._instance = super(Singleton, cls).__new__(cls)
    return cls._instance
  2. Thread-Safe Implementation - Use locks for multi-threaded environments

    _lock = threading.Lock()
    with cls._lock:
    if cls._instance is None:
    cls._instance = super(Singleton, cls).__new__(cls)
  3. Lazy vs Eager Initialization - Choose based on needs

    • Lazy: Create when first accessed
    • Eager: Create when class is loaded
  4. Serialization (Java) - Implement readResolve() to prevent new instances

Good examples to mention:

  • Database Connection Manager - Single connection pool
  • Logger - One logger instance for the application
  • Configuration Manager - Application-wide settings
  • Cache Manager - Single cache instance
  • Thread Pool Manager - Single thread pool

Mistakes interviewers watch for:

  1. Not Thread-Safe - Can create multiple instances

    • ❌ Bad: No synchronization in multi-threaded environment
    • ✅ Good: Use double-check locking or synchronized methods
  2. Public Constructor - Allows external instantiation

    • ❌ Bad: Public constructor
    • ✅ Good: Private constructor
  3. Serialization Issues (Java) - Deserialization creates new instance

    • ❌ Bad: Serializable without readResolve()
    • ✅ Good: Implement readResolve() method
  4. Overuse - Using Singleton for everything

    • ❌ Bad: Singleton for classes that need multiple instances
    • ✅ Good: Use Singleton only when exactly one instance is needed

Be ready to discuss:

  • Dependency Injection - Pass instance as parameter
  • Service Locator - Central registry for services
  • Monostate Pattern - All instances share the same state
  • Factory Pattern - Can return the same instance

When to use alternatives:

“If testing is a priority, I might use dependency injection instead of Singleton. This makes it easier to mock dependencies and test components in isolation.”

Before your interview, make sure you can:

  • Define Singleton Pattern clearly in one sentence
  • Explain when to use it (with examples)
  • Describe the structure and components
  • Implement thread-safe Singleton
  • Discuss thread safety and synchronization
  • List benefits and trade-offs
  • Identify common mistakes
  • Compare with alternatives (static class, dependency injection)
  • Give 2-3 real-world examples
  • Explain problems with Singleton Pattern

Remember: Singleton Pattern ensures only one instance exists, providing controlled access to shared resources. Use it judiciously - it’s powerful but can make code harder to test! 🎯