Singleton Pattern
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.
Why Singleton Pattern?
Section titled “Why Singleton Pattern?”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.
What’s the Use of Singleton Pattern?
Section titled “What’s the Use of Singleton Pattern?”The Singleton Pattern is useful when:
- You need exactly one instance - Multiple instances would cause problems
- Shared resource management - Database connections, file systems, caches
- Global configuration - Application settings that should be consistent
- Logging systems - One logger instance for the entire application
- Resource-intensive objects - Objects that are expensive to create (only create once)
- 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
Simple Example: The Pizza Shop Manager
Section titled “Simple Example: The Pizza Shop Manager”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Singleton Pattern works in practice - showing how multiple clients get the same instance:
The Problem
Section titled “The Problem”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:
# ❌ 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 ordertrack_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)// ❌ Without Singleton Pattern - Multiple instances possible!
public class PizzaShopManager { private List<String> orders = new ArrayList<>();
public PizzaShopManager() { System.out.println("Creating a new PizzaShopManager instance!"); }
public void addOrder(String order) { orders.add(order); System.out.println("Order added: " + order + ". Total orders: " + orders.size()); }
public List<String> getOrders() { return orders; }}
// Problem: Each part of code creates its own manager!public class OrderProcessor { public static void processOrder() { PizzaShopManager manager1 = new PizzaShopManager(); // Creates instance 1 manager1.addOrder("Margherita"); }}
public class OrderTracker { public static void trackOrders() { PizzaShopManager manager2 = new PizzaShopManager(); // Creates instance 2 - Different instance! manager2.addOrder("Pepperoni"); }}
public class ReportGenerator { public static List<String> generateReport() { PizzaShopManager manager3 = new PizzaShopManager(); // Creates instance 3 - Yet another instance! return manager3.getOrders(); }}
// Usage - Problem!public class Main { public static void main(String[] args) { OrderProcessor.processOrder(); // Manager 1 has 1 order OrderTracker.trackOrders(); // Manager 2 has 1 order (different instance!) List<String> report = ReportGenerator.generateReport(); // 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) }}// ❌ Without Singleton Pattern - Multiple instances possible!
class PizzaShopManager { orders: string[] = [];
constructor() { console.log("Creating a new PizzaShopManager instance!"); }
addOrder(order: string): void { this.orders.push(order); console.log(`Order added: ${order}. Total orders: ${this.orders.length}`); }
getOrders(): string[] { return this.orders; }}
// Problem: Each part of code creates its own manager!function processOrder(): PizzaShopManager { const manager1 = new PizzaShopManager(); // Creates instance 1 manager1.addOrder("Margherita"); return manager1;}
function trackOrders(): PizzaShopManager { const manager2 = new PizzaShopManager(); // Creates instance 2 - Different instance! manager2.addOrder("Pepperoni"); return manager2;}
function generateReport(): string[] { const manager3 = new PizzaShopManager(); // Creates instance 3 - Yet another instance! return manager3.getOrders();}
// Usage - Problem!processOrder(); // Manager 1 has 1 ordertrackOrders(); // Manager 2 has 1 order (different instance!)const report = generateReport(); // 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)// ❌ Without Singleton Pattern - Multiple instances possible!
#include <iostream>#include <vector>#include <string>
class PizzaShopManager {private: std::vector<std::string> orders;
public: PizzaShopManager() { std::cout << "Creating a new PizzaShopManager instance!" << std::endl; }
void addOrder(const std::string& order) { orders.push_back(order); std::cout << "Order added: " << order << ". Total orders: " << orders.size() << std::endl; }
std::vector<std::string> getOrders() const { return orders; }};
// Problem: Each part of code creates its own manager!PizzaShopManager processOrder() { PizzaShopManager manager1; // Creates instance 1 manager1.addOrder("Margherita"); return manager1;}
PizzaShopManager trackOrders() { PizzaShopManager manager2; // Creates instance 2 - Different instance! manager2.addOrder("Pepperoni"); return manager2;}
std::vector<std::string> generateReport() { PizzaShopManager manager3; // Creates instance 3 - Yet another instance! return manager3.getOrders();}
// Usage - Problem!int main() { processOrder(); // Manager 1 has 1 order trackOrders(); // Manager 2 has 1 order (different instance!) auto report = generateReport(); // 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)
return 0;}// ❌ Without Singleton Pattern - Multiple instances possible!
using System;using System.Collections.Generic;
public class PizzaShopManager{ private List<string> orders = new List<string>();
public PizzaShopManager() { Console.WriteLine("Creating a new PizzaShopManager instance!"); }
public void AddOrder(string order) { orders.Add(order); Console.WriteLine($"Order added: {order}. Total orders: {orders.Count}"); }
public List<string> GetOrders() { return orders; }}
// Problem: Each part of code creates its own manager!public class OrderProcessor{ public static void ProcessOrder() { PizzaShopManager manager1 = new PizzaShopManager(); // Creates instance 1 manager1.AddOrder("Margherita"); }}
public class OrderTracker{ public static void TrackOrders() { PizzaShopManager manager2 = new PizzaShopManager(); // Creates instance 2 - Different instance! manager2.AddOrder("Pepperoni"); }}
public class ReportGenerator{ public static List<string> GenerateReport() { PizzaShopManager manager3 = new PizzaShopManager(); // Creates instance 3 - Yet another instance! return manager3.GetOrders(); }}
// Usage - Problem!class Program{ static void Main() { OrderProcessor.ProcessOrder(); // Manager 1 has 1 order OrderTracker.TrackOrders(); // Manager 2 has 1 order (different instance!) var report = ReportGenerator.GenerateReport(); // 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) }}package main
import ( "fmt")
type PizzaShopManager struct { orders []string}
func NewPizzaShopManager() *PizzaShopManager { fmt.Println("Creating a new PizzaShopManager instance!") return &PizzaShopManager{orders: []string{}}}
func (m *PizzaShopManager) AddOrder(order string) { m.orders = append(m.orders, order) fmt.Printf("Order added: %s. Total orders: %d\n", order, len(m.orders))}
func (m *PizzaShopManager) GetOrders() []string { out := make([]string, len(m.orders)) copy(out, m.orders) return out}
// Problem: Each part of code creates its own manager!
func ProcessOrder() { manager1 := NewPizzaShopManager() // Creates instance 1 manager1.AddOrder("Margherita")}
func TrackOrders() { manager2 := NewPizzaShopManager() // Creates instance 2 - Different instance! manager2.AddOrder("Pepperoni")}
func GenerateReport() []string { manager3 := NewPizzaShopManager() // Creates instance 3 - Yet another instance! return manager3.GetOrders()}
func main() { ProcessOrder() // Manager 1 has 1 order TrackOrders() // Manager 2 has 1 order (different instance!) report := GenerateReport() // Manager 3 has 0 orders (yet another instance!) _ = report
// Problems: // - Three different managers with different states // - Orders are scattered across instances // - No single source of truth // - Memory waste (3 instances instead of 1)}// The Problemstruct Config { database_url: String,}fn load_config_everywhere() -> Config { Config { database_url: "postgres://localhost".into(), }}Problems:
- Multiple instances with different states
- No single source of truth
- Memory waste
- Inconsistent data across the application
The Solution: Singleton Pattern
Section titled “The Solution: Singleton Pattern”Class Structure
Section titled “Class Structure”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 ordertrack_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!import java.util.ArrayList;import java.util.List;
public class PizzaShopManager { // Private static instance variable private static PizzaShopManager instance; private List<String> orders;
// Private constructor - prevents external instantiation private PizzaShopManager() { this.orders = new ArrayList<>(); System.out.println("Creating the one and only PizzaShopManager instance!"); }
// Public static method to get the instance public static PizzaShopManager getInstance() { if (instance == null) { instance = new PizzaShopManager(); } else { System.out.println("PizzaShopManager instance already exists - returning existing one!"); } return instance; }
public void addOrder(String order) { orders.add(order); System.out.println("Order added: " + order + ". Total orders: " + orders.size()); }
public List<String> getOrders() { return new ArrayList<>(orders); // Return a copy to prevent external modification }}
// Usage - All get the same instance!public class OrderProcessor { public static void processOrder() { PizzaShopManager manager1 = PizzaShopManager.getInstance(); // Creates the instance manager1.addOrder("Margherita"); }}
public class OrderTracker { public static void trackOrders() { PizzaShopManager manager2 = PizzaShopManager.getInstance(); // Returns existing instance! manager2.addOrder("Pepperoni"); }}
public class ReportGenerator { public static List<String> generateReport() { PizzaShopManager manager3 = PizzaShopManager.getInstance(); // Returns existing instance! return manager3.getOrders(); }}
// Usage - All use the same instance!public class Main { public static void main(String[] args) { OrderProcessor.processOrder(); // Manager has 1 order OrderTracker.trackOrders(); // Same manager now has 2 orders! List<String> report = ReportGenerator.generateReport(); // Same manager - shows 2 orders!
// Verify all are the same instance PizzaShopManager m1 = PizzaShopManager.getInstance(); PizzaShopManager m2 = PizzaShopManager.getInstance(); System.out.println("All managers are the same: " + (m1 == m2)); // true! }}class PizzaShopManager { /** Singleton class - only one instance can exist */
private static instance: PizzaShopManager | null = null; // Class variable to store the single instance private orders: string[];
// Private constructor - prevents external instantiation private constructor() { this.orders = []; console.log("Creating the one and only PizzaShopManager instance!"); }
// Public static method to get the instance public static getInstance(): PizzaShopManager { if (PizzaShopManager.instance === null) { PizzaShopManager.instance = new PizzaShopManager(); } else { console.log("PizzaShopManager instance already exists - returning existing one!"); } return PizzaShopManager.instance; }
public addOrder(order: string): void { /** Add an order */ this.orders.push(order); console.log(`Order added: ${order}. Total orders: ${this.orders.length}`); }
public getOrders(): string[] { /** Get all orders */ return [...this.orders]; // Return a copy to prevent external modification }}
// Usage - All get the same instance!function processOrder(): PizzaShopManager { const manager1 = PizzaShopManager.getInstance(); // Creates the instance manager1.addOrder("Margherita"); return manager1;}
function trackOrders(): PizzaShopManager { const manager2 = PizzaShopManager.getInstance(); // Returns existing instance! manager2.addOrder("Pepperoni"); return manager2;}
function generateReport(): string[] { const manager3 = PizzaShopManager.getInstance(); // Returns existing instance! return manager3.getOrders();}
// Usage - All use the same instance!processOrder(); // Manager has 1 ordertrackOrders(); // Same manager now has 2 orders!const report = generateReport(); // Same manager - shows 2 orders!
console.log(`All managers are the same: ${processOrder() === trackOrders()}`); // true!#include <iostream>#include <vector>#include <string>#include <memory>
class PizzaShopManager {private: // Private static instance variable static PizzaShopManager* instance; std::vector<std::string> orders;
// Private constructor - prevents external instantiation PizzaShopManager() { std::cout << "Creating the one and only PizzaShopManager instance!" << std::endl; }
// Delete copy constructor and assignment operator PizzaShopManager(const PizzaShopManager&) = delete; PizzaShopManager& operator=(const PizzaShopManager&) = delete;
public: // Public static method to get the instance static PizzaShopManager* getInstance() { if (instance == nullptr) { instance = new PizzaShopManager(); } else { std::cout << "PizzaShopManager instance already exists - returning existing one!" << std::endl; } return instance; }
void addOrder(const std::string& order) { orders.push_back(order); std::cout << "Order added: " << order << ". Total orders: " << orders.size() << std::endl; }
std::vector<std::string> getOrders() const { return orders; // Return a copy to prevent external modification }};
// Initialize static memberPizzaShopManager* PizzaShopManager::instance = nullptr;
// Usage - All get the same instance!void processOrder() { PizzaShopManager* manager1 = PizzaShopManager::getInstance(); // Creates the instance manager1->addOrder("Margherita");}
void trackOrders() { PizzaShopManager* manager2 = PizzaShopManager::getInstance(); // Returns existing instance! manager2->addOrder("Pepperoni");}
std::vector<std::string> generateReport() { PizzaShopManager* manager3 = PizzaShopManager::getInstance(); // Returns existing instance! return manager3->getOrders();}
// Usage - All use the same instance!int main() { processOrder(); // Manager has 1 order trackOrders(); // Same manager now has 2 orders! auto report = generateReport(); // Same manager - shows 2 orders!
// Verify all are the same instance PizzaShopManager* m1 = PizzaShopManager::getInstance(); PizzaShopManager* m2 = PizzaShopManager::getInstance(); std::cout << "All managers are the same: " << (m1 == m2) << std::endl; // true!
return 0;}using System;using System.Collections.Generic;
public class PizzaShopManager{ // Private static instance variable private static PizzaShopManager instance; private List<string> orders;
// Private constructor - prevents external instantiation private PizzaShopManager() { orders = new List<string>(); Console.WriteLine("Creating the one and only PizzaShopManager instance!"); }
// Public static method to get the instance public static PizzaShopManager GetInstance() { if (instance == null) { instance = new PizzaShopManager(); } else { Console.WriteLine("PizzaShopManager instance already exists - returning existing one!"); } return instance; }
public void AddOrder(string order) { orders.Add(order); Console.WriteLine($"Order added: {order}. Total orders: {orders.Count}"); }
public List<string> GetOrders() { return new List<string>(orders); // Return a copy to prevent external modification }}
// Usage - All get the same instance!public class OrderProcessor{ public static void ProcessOrder() { PizzaShopManager manager1 = PizzaShopManager.GetInstance(); // Creates the instance manager1.AddOrder("Margherita"); }}
public class OrderTracker{ public static void TrackOrders() { PizzaShopManager manager2 = PizzaShopManager.GetInstance(); // Returns existing instance! manager2.AddOrder("Pepperoni"); }}
public class ReportGenerator{ public static List<string> GenerateReport() { PizzaShopManager manager3 = PizzaShopManager.GetInstance(); // Returns existing instance! return manager3.GetOrders(); }}
// Usage - All use the same instance!class Program{ static void Main() { OrderProcessor.ProcessOrder(); // Manager has 1 order OrderTracker.TrackOrders(); // Same manager now has 2 orders! var report = ReportGenerator.GenerateReport(); // Same manager - shows 2 orders!
// Verify all are the same instance PizzaShopManager m1 = PizzaShopManager.GetInstance(); PizzaShopManager m2 = PizzaShopManager.GetInstance(); Console.WriteLine($"All managers are the same: {ReferenceEquals(m1, m2)}"); // true! }}package main
import ( "fmt" "sync")
type PizzaShopManager struct { orders []string}
var ( psmInstance *PizzaShopManager psmMu sync.Mutex)
func GetPizzaShopInstance() *PizzaShopManager { psmMu.Lock() defer psmMu.Unlock() if psmInstance == nil { psmInstance = &PizzaShopManager{orders: []string{}} fmt.Println("Creating the one and only PizzaShopManager instance!") } else { fmt.Println("PizzaShopManager instance already exists - returning existing one!") } return psmInstance}
func (m *PizzaShopManager) AddOrder(order string) { m.orders = append(m.orders, order) fmt.Printf("Order added: %s. Total orders: %d\n", order, len(m.orders))}
func (m *PizzaShopManager) GetOrders() []string { out := make([]string, len(m.orders)) copy(out, m.orders) return out}
type OrderProcessor struct{}
func (OrderProcessor) ProcessOrder() { manager1 := GetPizzaShopInstance() // Creates the instance manager1.AddOrder("Margherita")}
type OrderTracker struct{}
func (OrderTracker) TrackOrders() { manager2 := GetPizzaShopInstance() // Returns existing instance! manager2.AddOrder("Pepperoni")}
type ReportGenerator struct{}
func (ReportGenerator) GenerateReport() []string { manager3 := GetPizzaShopInstance() // Returns existing instance! return manager3.GetOrders()}
func main() { var op OrderProcessor var ot OrderTracker var rg ReportGenerator
op.ProcessOrder() // Manager has 1 order ot.TrackOrders() // Same manager now has 2 orders! report := rg.GenerateReport() // Same manager - shows 2 orders! _ = report
m1 := GetPizzaShopInstance() m2 := GetPizzaShopInstance() fmt.Printf("All managers are the same: %v\n", m1 == m2) // true!}// Class Structureuse std::sync::OnceLock;struct Config { database_url: String,}static CONFIG: OnceLock<Config> = OnceLock::new();fn config() -> &'static Config { CONFIG.get_or_init(|| Config { database_url: "postgres://localhost".into(), })}✅ Single instance - Only one instance exists throughout the application
✅ Global access - Easy to access from anywhere
✅ Consistent state - All code uses the same instance
✅ Resource efficient - No memory waste from multiple instances
✅ Controlled access - You control how and when the instance is created
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.
The Problem
Section titled “The Problem”You’re building an application that needs database access. Creating multiple connection managers would waste resources and cause connection pool conflicts. Without Singleton Pattern:
# ❌ 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 timesimport java.util.ArrayList;import java.util.List;
// ❌ Without Singleton Pattern - Multiple connection managers!
public class DatabaseConnectionManager { private List<String> connectionPool; private int maxConnections = 10;
public DatabaseConnectionManager() { this.connectionPool = new ArrayList<>(); System.out.println("Creating DatabaseConnectionManager with " + maxConnections + " max connections"); // Expensive initialization - connecting to database, setting up pool, etc. }
public String getConnection() { if (connectionPool.size() < maxConnections) { String conn = "Connection-" + (connectionPool.size() + 1); connectionPool.add(conn); return conn; } throw new RuntimeException("Connection pool exhausted!"); }
public void releaseConnection(String conn) { connectionPool.remove(conn); }}
// Problem: Each module creates its own manager!public class UserService { private DatabaseConnectionManager dbManager;
public UserService() { this.dbManager = new DatabaseConnectionManager(); // Creates instance 1 }
public void getUser(int userId) { String conn = dbManager.getConnection(); // Use connection... dbManager.releaseConnection(conn); }}
public class OrderService { private DatabaseConnectionManager dbManager;
public OrderService() { this.dbManager = new DatabaseConnectionManager(); // Creates instance 2 - Different pool! }
public void createOrder(String orderData) { String conn = dbManager.getConnection(); // Use connection... dbManager.releaseConnection(conn); }}
public class ProductService { private DatabaseConnectionManager dbManager;
public ProductService() { this.dbManager = new DatabaseConnectionManager(); // Creates instance 3 - Yet another pool! }
public void getProduct(int productId) { String conn = dbManager.getConnection(); // Use connection... dbManager.releaseConnection(conn); }}
// Problems:// - Three separate connection pools (30 total connections instead of 10!)// - Resource waste// - No shared connection pool// - Expensive initialization happens 3 times// ❌ Without Singleton Pattern - Multiple connection managers!
class DatabaseConnectionManager { private connectionPool: string[] = []; private maxConnections: number = 10;
constructor() { console.log(`Creating DatabaseConnectionManager with ${this.maxConnections} max connections`); // Expensive initialization - connecting to database, setting up pool, etc. }
getConnection(): string { if (this.connectionPool.length < this.maxConnections) { const conn = `Connection-${this.connectionPool.length + 1}`; this.connectionPool.push(conn); return conn; } throw new Error("Connection pool exhausted!"); }
releaseConnection(conn: string): void { const index = this.connectionPool.indexOf(conn); if (index > -1) { this.connectionPool.splice(index, 1); } }}
// Problem: Each module creates its own manager!class UserService { private dbManager: DatabaseConnectionManager;
constructor() { this.dbManager = new DatabaseConnectionManager(); // Creates instance 1 }
getUser(userId: number): void { const conn = this.dbManager.getConnection(); // Use connection... this.dbManager.releaseConnection(conn); }}
class OrderService { private dbManager: DatabaseConnectionManager;
constructor() { this.dbManager = new DatabaseConnectionManager(); // Creates instance 2 - Different pool! }
createOrder(orderData: string): void { const conn = this.dbManager.getConnection(); // Use connection... this.dbManager.releaseConnection(conn); }}
class ProductService { private dbManager: DatabaseConnectionManager;
constructor() { this.dbManager = new DatabaseConnectionManager(); // Creates instance 3 - Yet another pool! }
getProduct(productId: number): void { const conn = this.dbManager.getConnection(); // Use connection... this.dbManager.releaseConnection(conn); }}
// Problems:// - Three separate connection pools (30 total connections instead of 10!)// - Resource waste// - No shared connection pool// - Expensive initialization happens 3 times// ❌ Without Singleton Pattern - Multiple connection managers!
#include <iostream>#include <vector>#include <string>#include <stdexcept>#include <algorithm>
class DatabaseConnectionManager {private: std::vector<std::string> connectionPool; int maxConnections = 10;
public: DatabaseConnectionManager() { std::cout << "Creating DatabaseConnectionManager with " << maxConnections << " max connections" << std::endl; // Expensive initialization - connecting to database, setting up pool, etc. }
std::string getConnection() { if (connectionPool.size() < maxConnections) { std::string conn = "Connection-" + std::to_string(connectionPool.size() + 1); connectionPool.push_back(conn); return conn; } throw std::runtime_error("Connection pool exhausted!"); }
void releaseConnection(const std::string& conn) { auto it = std::find(connectionPool.begin(), connectionPool.end(), conn); if (it != connectionPool.end()) { connectionPool.erase(it); } }};
// Problem: Each module creates its own manager!class UserService {private: DatabaseConnectionManager dbManager;
public: UserService() : dbManager() { // Creates instance 1 }
void getUser(int userId) { std::string conn = dbManager.getConnection(); // Use connection... dbManager.releaseConnection(conn); }};
class OrderService {private: DatabaseConnectionManager dbManager;
public: OrderService() : dbManager() { // Creates instance 2 - Different pool! }
void createOrder(const std::string& orderData) { std::string conn = dbManager.getConnection(); // Use connection... dbManager.releaseConnection(conn); }};
class ProductService {private: DatabaseConnectionManager dbManager;
public: ProductService() : dbManager() { // Creates instance 3 - Yet another pool! }
void getProduct(int productId) { std::string conn = dbManager.getConnection(); // Use connection... dbManager.releaseConnection(conn); }};
// Problems:// - Three separate connection pools (30 total connections instead of 10!)// - Resource waste// - No shared connection pool// - Expensive initialization happens 3 timesusing System;using System.Collections.Generic;
// ❌ Without Singleton Pattern - Multiple connection managers!
public class DatabaseConnectionManager{ private List<string> connectionPool; private int maxConnections = 10;
public DatabaseConnectionManager() { connectionPool = new List<string>(); Console.WriteLine($"Creating DatabaseConnectionManager with {maxConnections} max connections"); // Expensive initialization - connecting to database, setting up pool, etc. }
public string GetConnection() { if (connectionPool.Count < maxConnections) { string conn = $"Connection-{connectionPool.Count + 1}"; connectionPool.Add(conn); return conn; } throw new Exception("Connection pool exhausted!"); }
public void ReleaseConnection(string conn) { connectionPool.Remove(conn); }}
// Problem: Each module creates its own manager!public class UserService{ private DatabaseConnectionManager dbManager;
public UserService() { dbManager = new DatabaseConnectionManager(); // Creates instance 1 }
public void GetUser(int userId) { string conn = dbManager.GetConnection(); // Use connection... dbManager.ReleaseConnection(conn); }}
public class OrderService{ private DatabaseConnectionManager dbManager;
public OrderService() { dbManager = new DatabaseConnectionManager(); // Creates instance 2 - Different pool! }
public void CreateOrder(string orderData) { string conn = dbManager.GetConnection(); // Use connection... dbManager.ReleaseConnection(conn); }}
public class ProductService{ private DatabaseConnectionManager dbManager;
public ProductService() { dbManager = new DatabaseConnectionManager(); // Creates instance 3 - Yet another pool! }
public void GetProduct(int productId) { string conn = dbManager.GetConnection(); // Use connection... dbManager.ReleaseConnection(conn); }}
// Problems:// - Three separate connection pools (30 total connections instead of 10!)// - Resource waste// - No shared connection pool// - Expensive initialization happens 3 timespackage main
import ( "errors" "fmt")
// ❌ Without Singleton Pattern - Multiple connection managers!
type DatabaseConnectionManager struct { connectionPool []string maxConnections int}
func NewDatabaseConnectionManager() *DatabaseConnectionManager { m := &DatabaseConnectionManager{ connectionPool: []string{}, maxConnections: 10, } fmt.Printf("Creating DatabaseConnectionManager with %d max connections\n", m.maxConnections) return m}
func (m *DatabaseConnectionManager) GetConnection() (string, error) { if len(m.connectionPool) < m.maxConnections { conn := fmt.Sprintf("Connection-%d", len(m.connectionPool)+1) m.connectionPool = append(m.connectionPool, conn) return conn, nil } return "", errors.New("Connection pool exhausted!")}
func (m *DatabaseConnectionManager) ReleaseConnection(conn string) { for i, c := range m.connectionPool { if c == conn { m.connectionPool = append(m.connectionPool[:i], m.connectionPool[i+1:]...) return } }}
// Problem: Each module creates its own manager!
type UserService struct { db *DatabaseConnectionManager}
func NewUserService() *UserService { return &UserService{db: NewDatabaseConnectionManager()} // Creates instance 1}
func (s *UserService) GetUser(userID int) { conn, _ := s.db.GetConnection() _ = conn _ = userID // Use connection... s.db.ReleaseConnection(conn)}
type OrderService struct { db *DatabaseConnectionManager}
func NewOrderService() *OrderService { return &OrderService{db: NewDatabaseConnectionManager()} // Creates instance 2 - Different pool!}
func (s *OrderService) CreateOrder(orderData string) { conn, _ := s.db.GetConnection() _ = orderData // Use connection... s.db.ReleaseConnection(conn)}
type ProductService struct { db *DatabaseConnectionManager}
func NewProductService() *ProductService { return &ProductService{db: NewDatabaseConnectionManager()} // Creates instance 3 - Yet another pool!}
func (s *ProductService) GetProduct(productID int) { conn, _ := s.db.GetConnection() _ = productID // Use connection... s.db.ReleaseConnection(conn)}
// Problems:// - Three separate connection pools (30 total connections instead of 10!)// - Resource waste// - No shared connection pool// - Expensive initialization happens 3 times// The Problemstruct Config { database_url: String,}fn load_config_everywhere() -> Config { Config { database_url: "postgres://localhost".into(), }}- Expensive initialization happens multiple times
- No shared connection management
- Potential connection pool exhaustion
The Solution: Singleton Pattern
Section titled “The Solution: Singleton Pattern”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)import java.util.ArrayList;import java.util.List;
public class DatabaseConnectionManager { // Private static instance variable private static DatabaseConnectionManager instance; private static final Object lock = new Object(); // For thread safety
private List<String> connectionPool; private int maxConnections = 10;
// Private constructor - prevents external instantiation private DatabaseConnectionManager() { this.connectionPool = new ArrayList<>(); System.out.println("Creating the one and only DatabaseConnectionManager!"); // Expensive initialization happens only once! System.out.println("Initialized connection pool with " + maxConnections + " max connections"); }
// Public static method to get the instance (thread-safe) public static DatabaseConnectionManager getInstance() { if (instance == null) { synchronized (lock) { // Double-check locking pattern if (instance == null) { instance = new DatabaseConnectionManager(); } } } return instance; }
public String getConnection() { if (connectionPool.size() < maxConnections) { String conn = "Connection-" + (connectionPool.size() + 1); connectionPool.add(conn); System.out.println("Got connection: " + conn + ". Pool size: " + connectionPool.size() + "/" + maxConnections); return conn; } throw new RuntimeException("Connection pool exhausted!"); }
public void releaseConnection(String conn) { connectionPool.remove(conn); System.out.println("Released connection: " + conn + ". Pool size: " + connectionPool.size() + "/" + maxConnections); }}
// All services use the same instance!public class UserService { private DatabaseConnectionManager dbManager;
public UserService() { this.dbManager = DatabaseConnectionManager.getInstance(); // Gets the singleton instance }
public void getUser(int userId) { String conn = dbManager.getConnection(); System.out.println("UserService: Using " + conn + " to get user " + userId); dbManager.releaseConnection(conn); }}
public class OrderService { private DatabaseConnectionManager dbManager;
public OrderService() { this.dbManager = DatabaseConnectionManager.getInstance(); // Gets the same singleton instance! }
public void createOrder(String orderData) { String conn = dbManager.getConnection(); System.out.println("OrderService: Using " + conn + " to create order"); dbManager.releaseConnection(conn); }}
public class ProductService { private DatabaseConnectionManager dbManager;
public ProductService() { this.dbManager = DatabaseConnectionManager.getInstance(); // Gets the same singleton instance! }
public void getProduct(int productId) { String conn = dbManager.getConnection(); System.out.println("ProductService: Using " + conn + " to get product " + productId); dbManager.releaseConnection(conn); }}
// Usage - All services share the same connection pool!public class Main { public static void main(String[] args) { UserService userService = new UserService(); OrderService orderService = new OrderService(); ProductService productService = new ProductService();
// All use the same DatabaseConnectionManager instance! System.out.println("Same instance: " + (userService.dbManager == orderService.dbManager && orderService.dbManager == productService.dbManager)); // true!
userService.getUser(1); orderService.createOrder("Pizza"); productService.getProduct(1); }}class DatabaseConnectionManager { /** Singleton Database Connection Manager */
private static instance: DatabaseConnectionManager | null = null; private connectionPool: string[] = []; private maxConnections: number = 10;
// Private constructor - prevents external instantiation private constructor() { console.log("Creating the one and only DatabaseConnectionManager!"); // Expensive initialization happens only once! console.log(`Initialized connection pool with ${this.maxConnections} max connections`); }
// Public static method to get the instance public static getInstance(): DatabaseConnectionManager { if (DatabaseConnectionManager.instance === null) { DatabaseConnectionManager.instance = new DatabaseConnectionManager(); } return DatabaseConnectionManager.instance; }
public getConnection(): string { /** Get a connection from the pool */ if (this.connectionPool.length < this.maxConnections) { const conn = `Connection-${this.connectionPool.length + 1}`; this.connectionPool.push(conn); console.log(`Got connection: ${conn}. Pool size: ${this.connectionPool.length}/${this.maxConnections}`); return conn; } throw new Error("Connection pool exhausted!"); }
public releaseConnection(conn: string): void { /** Release a connection back to the pool */ const index = this.connectionPool.indexOf(conn); if (index > -1) { this.connectionPool.splice(index, 1); console.log(`Released connection: ${conn}. Pool size: ${this.connectionPool.length}/${this.maxConnections}`); } }}
// All services use the same instance!class UserService { private dbManager: DatabaseConnectionManager;
constructor() { this.dbManager = DatabaseConnectionManager.getInstance(); // Gets the singleton instance }
getUser(userId: number): void { const conn = this.dbManager.getConnection(); console.log(`UserService: Using ${conn} to get user ${userId}`); this.dbManager.releaseConnection(conn); }}
class OrderService { private dbManager: DatabaseConnectionManager;
constructor() { this.dbManager = DatabaseConnectionManager.getInstance(); // Gets the same singleton instance! }
createOrder(orderData: any): void { const conn = this.dbManager.getConnection(); console.log(`OrderService: Using ${conn} to create order`); this.dbManager.releaseConnection(conn); }}
class ProductService { private dbManager: DatabaseConnectionManager;
constructor() { this.dbManager = DatabaseConnectionManager.getInstance(); // Gets the same singleton instance! }
getProduct(productId: number): void { const conn = this.dbManager.getConnection(); console.log(`ProductService: Using ${conn} to get product ${productId}`); this.dbManager.releaseConnection(conn); }}
// Usage - All services share the same connection pool!const userService = new UserService();const orderService = new OrderService();const productService = new ProductService();
// All use the same DatabaseConnectionManager instance!console.log(`Same instance: ${DatabaseConnectionManager.getInstance() === DatabaseConnectionManager.getInstance()}`); // true!
userService.getUser(1);orderService.createOrder({item: "Pizza"});productService.getProduct(1);#include <iostream>#include <vector>#include <string>#include <mutex>#include <stdexcept>
class DatabaseConnectionManager {private: static DatabaseConnectionManager* instance; static std::mutex mtx; // For thread safety
std::vector<std::string> connectionPool; int maxConnections = 10;
// Private constructor - prevents external instantiation DatabaseConnectionManager() { std::cout << "Creating the one and only DatabaseConnectionManager!" << std::endl; // Expensive initialization happens only once! std::cout << "Initialized connection pool with " << maxConnections << " max connections" << std::endl; }
// Delete copy constructor and assignment operator DatabaseConnectionManager(const DatabaseConnectionManager&) = delete; DatabaseConnectionManager& operator=(const DatabaseConnectionManager&) = delete;
public: // Public static method to get the instance static DatabaseConnectionManager* getInstance() { if (instance == nullptr) { std::lock_guard<std::mutex> lock(mtx); // Double-check locking pattern if (instance == nullptr) { instance = new DatabaseConnectionManager(); } } return instance; }
std::string getConnection() { /** Get a connection from the pool */ if (connectionPool.size() < maxConnections) { std::string conn = "Connection-" + std::to_string(connectionPool.size() + 1); connectionPool.push_back(conn); std::cout << "Got connection: " << conn << ". Pool size: " << connectionPool.size() << "/" << maxConnections << std::endl; return conn; } throw std::runtime_error("Connection pool exhausted!"); }
void releaseConnection(const std::string& conn) { /** Release a connection back to the pool */ auto it = std::find(connectionPool.begin(), connectionPool.end(), conn); if (it != connectionPool.end()) { connectionPool.erase(it); std::cout << "Released connection: " << conn << ". Pool size: " << connectionPool.size() << "/" << maxConnections << std::endl; } }};
// Initialize static membersDatabaseConnectionManager* DatabaseConnectionManager::instance = nullptr;std::mutex DatabaseConnectionManager::mtx;
// All services use the same instance!class UserService {private: DatabaseConnectionManager* dbManager;
public: UserService() { dbManager = DatabaseConnectionManager::getInstance(); // Gets the singleton instance }
void getUser(int userId) { std::string conn = dbManager->getConnection(); std::cout << "UserService: Using " << conn << " to get user " << userId << std::endl; dbManager->releaseConnection(conn); }};
class OrderService {private: DatabaseConnectionManager* dbManager;
public: OrderService() { dbManager = DatabaseConnectionManager::getInstance(); // Gets the same singleton instance! }
void createOrder(const std::string& orderData) { std::string conn = dbManager->getConnection(); std::cout << "OrderService: Using " << conn << " to create order" << std::endl; dbManager->releaseConnection(conn); }};
class ProductService {private: DatabaseConnectionManager* dbManager;
public: ProductService() { dbManager = DatabaseConnectionManager::getInstance(); // Gets the same singleton instance! }
void getProduct(int productId) { std::string conn = dbManager->getConnection(); std::cout << "ProductService: Using " << conn << " to get product " << productId << std::endl; dbManager->releaseConnection(conn); }};
// Usage - All services share the same connection pool!int main() { UserService userService; OrderService orderService; ProductService productService;
// All use the same DatabaseConnectionManager instance! std::cout << "Same instance: " << (DatabaseConnectionManager::getInstance() == DatabaseConnectionManager::getInstance()) << std::endl; // true!
userService.getUser(1); orderService.createOrder("Pizza"); productService.getProduct(1);
return 0;}using System;using System.Collections.Generic;
public class DatabaseConnectionManager{ // Private static instance variable private static DatabaseConnectionManager instance; private static readonly object lockObj = new object(); // For thread safety
private List<string> connectionPool; private int maxConnections = 10;
// Private constructor - prevents external instantiation private DatabaseConnectionManager() { connectionPool = new List<string>(); Console.WriteLine("Creating the one and only DatabaseConnectionManager!"); // Expensive initialization happens only once! Console.WriteLine($"Initialized connection pool with {maxConnections} max connections"); }
// Public static method to get the instance public static DatabaseConnectionManager GetInstance() { if (instance == null) { lock (lockObj) { // Double-check locking pattern if (instance == null) { instance = new DatabaseConnectionManager(); } } } return instance; }
public string GetConnection() { /** Get a connection from the pool */ if (connectionPool.Count < maxConnections) { string conn = $"Connection-{connectionPool.Count + 1}"; connectionPool.Add(conn); Console.WriteLine($"Got connection: {conn}. Pool size: {connectionPool.Count}/{maxConnections}"); return conn; } throw new Exception("Connection pool exhausted!"); }
public void ReleaseConnection(string conn) { /** Release a connection back to the pool */ if (connectionPool.Contains(conn)) { connectionPool.Remove(conn); Console.WriteLine($"Released connection: {conn}. Pool size: {connectionPool.Count}/{maxConnections}"); } }}
// All services use the same instance!public class UserService{ private DatabaseConnectionManager dbManager;
public UserService() { dbManager = DatabaseConnectionManager.GetInstance(); // Gets the singleton instance }
public void GetUser(int userId) { string conn = dbManager.GetConnection(); Console.WriteLine($"UserService: Using {conn} to get user {userId}"); dbManager.ReleaseConnection(conn); }}
public class OrderService{ private DatabaseConnectionManager dbManager;
public OrderService() { dbManager = DatabaseConnectionManager.GetInstance(); // Gets the same singleton instance! }
public void CreateOrder(string orderData) { string conn = dbManager.GetConnection(); Console.WriteLine($"OrderService: Using {conn} to create order"); dbManager.ReleaseConnection(conn); }}
public class ProductService{ private DatabaseConnectionManager dbManager;
public ProductService() { dbManager = DatabaseConnectionManager.GetInstance(); // Gets the same singleton instance! }
public void GetProduct(int productId) { string conn = dbManager.GetConnection(); Console.WriteLine($"ProductService: Using {conn} to get product {productId}"); dbManager.ReleaseConnection(conn); }}
// Usage - All services share the same connection pool!class Program{ static void Main() { UserService userService = new UserService(); OrderService orderService = new OrderService(); ProductService productService = new ProductService();
// All use the same DatabaseConnectionManager instance! Console.WriteLine($"Same instance: {ReferenceEquals(DatabaseConnectionManager.GetInstance(), DatabaseConnectionManager.GetInstance())}"); // true!
userService.GetUser(1); orderService.CreateOrder("Pizza"); productService.GetProduct(1); }}package main
import ( "errors" "fmt" "sync")
type DatabaseConnectionManager struct { connectionPool []string maxConnections int}
var ( dbSingleton *DatabaseConnectionManager dbLock sync.Mutex)
func GetDatabaseInstance() *DatabaseConnectionManager { if dbSingleton == nil { dbLock.Lock() if dbSingleton == nil { dbSingleton = &DatabaseConnectionManager{ connectionPool: []string{}, maxConnections: 10, } fmt.Println("Creating the one and only DatabaseConnectionManager!") fmt.Printf("Initialized connection pool with %d max connections\n", dbSingleton.maxConnections) } dbLock.Unlock() } return dbSingleton}
func (m *DatabaseConnectionManager) GetConnection() (string, error) { if len(m.connectionPool) < m.maxConnections { conn := fmt.Sprintf("Connection-%d", len(m.connectionPool)+1) m.connectionPool = append(m.connectionPool, conn) fmt.Printf("Got connection: %s. Pool size: %d/%d\n", conn, len(m.connectionPool), m.maxConnections) return conn, nil } return "", errors.New("Connection pool exhausted!")}
func (m *DatabaseConnectionManager) ReleaseConnection(conn string) { for i, c := range m.connectionPool { if c == conn { m.connectionPool = append(m.connectionPool[:i], m.connectionPool[i+1:]...) fmt.Printf("Released connection: %s. Pool size: %d/%d\n", conn, len(m.connectionPool), m.maxConnections) return } }}
type UserService struct { db *DatabaseConnectionManager}
func NewUserService() *UserService { return &UserService{db: GetDatabaseInstance()}}
func (s *UserService) GetUser(userID int) { conn, _ := s.db.GetConnection() fmt.Printf("UserService: Using %s to get user %d\n", conn, userID) s.db.ReleaseConnection(conn)}
type OrderService struct { db *DatabaseConnectionManager}
func NewOrderService() *OrderService { return &OrderService{db: GetDatabaseInstance()}}
func (s *OrderService) CreateOrder(orderData string) { conn, _ := s.db.GetConnection() fmt.Printf("OrderService: Using %s to create order\n", conn) _ = orderData s.db.ReleaseConnection(conn)}
type ProductService struct { db *DatabaseConnectionManager}
func NewProductService() *ProductService { return &ProductService{db: GetDatabaseInstance()}}
func (s *ProductService) GetProduct(productID int) { conn, _ := s.db.GetConnection() fmt.Printf("ProductService: Using %s to get product %d\n", conn, productID) s.db.ReleaseConnection(conn)}
func main() { userService := NewUserService() orderService := NewOrderService() productService := NewProductService()
fmt.Printf("Same instance: %v\n", userService.db == orderService.db && orderService.db == productService.db)
userService.GetUser(1) orderService.CreateOrder("Pizza") productService.GetProduct(1)}// The Solution: Singleton Patternuse std::sync::OnceLock;struct Config { database_url: String,}static CONFIG: OnceLock<Config> = OnceLock::new();fn config() -> &'static Config { CONFIG.get_or_init(|| Config { database_url: "postgres://localhost".into(), })}Singleton Pattern Variants
Section titled “Singleton Pattern Variants”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:
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 accessedpublic class EagerSingleton { // Instance created immediately when class is loaded private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { // Private constructor }
public static EagerSingleton getInstance() { return instance; // Always return the same instance }}class EagerSingleton { /** Eager initialization - instance created at class load time */
private static instance: EagerSingleton = new EagerSingleton();
private constructor() { // Private constructor }
public static getInstance(): EagerSingleton { return EagerSingleton.instance; // Always return the same instance }}
// Instance is created when class is first accessedclass EagerSingleton {private: static EagerSingleton instance;
// Private constructor EagerSingleton() {}
// Delete copy constructor and assignment operator EagerSingleton(const EagerSingleton&) = delete; EagerSingleton& operator=(const EagerSingleton&) = delete;
public: static EagerSingleton& getInstance() { return instance; // Always return the same instance }};
// Initialize static member - instance created immediately when program startsEagerSingleton EagerSingleton::instance;
// Instance is created at program startpublic class EagerSingleton{ // Instance created immediately when class is loaded private static readonly EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { // Private constructor }
public static EagerSingleton GetInstance() { return instance; // Always return the same instance }}
// Instance is created when class is first accessedpackage main
type EagerSingleton struct{}
// Instance created at package init (same idea as class load)var eagerSingletonInstance = &EagerSingleton{}
func GetEagerSingleton() *EagerSingleton { return eagerSingletonInstance}
// Instance is created when class is first accessed// 1. Eager Initialization Simple Singletonuse std::sync::OnceLock;struct Config { database_url: String,}static CONFIG: OnceLock<Config> = OnceLock::new();fn config() -> &'static Config { CONFIG.get_or_init(|| Config { database_url: "postgres://localhost".into(), })}Pros: Simple, thread-safe
Cons: Instance created even if never used
2. Lazy Initialization (On-Demand)
Section titled “2. Lazy Initialization (On-Demand)”The instance is created only when first requested:
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 accessedpublic class LazySingleton { private static LazySingleton instance;
private LazySingleton() { // Private constructor }
public static LazySingleton getInstance() { if (instance == null) { System.out.println("Creating instance for the first time!"); instance = new LazySingleton(); } return instance; }}class LazySingleton { /** Lazy initialization - instance created only when needed */
private static instance: LazySingleton | null = null;
private constructor() { // Private constructor }
public static getInstance(): LazySingleton { if (LazySingleton.instance === null) { console.log("Creating instance for the first time!"); LazySingleton.instance = new LazySingleton(); } return LazySingleton.instance; }}
// Instance is created only when first accessed#include <iostream>
class LazySingleton {private: static LazySingleton* instance;
// Private constructor LazySingleton() {}
// Delete copy constructor and assignment operator LazySingleton(const LazySingleton&) = delete; LazySingleton& operator=(const LazySingleton&) = delete;
public: static LazySingleton* getInstance() { if (instance == nullptr) { std::cout << "Creating instance for the first time!" << std::endl; instance = new LazySingleton(); } return instance; }};
// Initialize static memberLazySingleton* LazySingleton::instance = nullptr;
// Instance is created only when first accessedusing System;
public class LazySingleton{ private static LazySingleton instance;
private LazySingleton() { // Private constructor }
public static LazySingleton GetInstance() { if (instance == null) { Console.WriteLine("Creating instance for the first time!"); instance = new LazySingleton(); } return instance; }}
// Instance is created only when first accessedpackage main
import "fmt"
type LazySingleton struct{}
var lazySingletonInstance *LazySingleton
func GetLazySingleton() *LazySingleton { if lazySingletonInstance == nil { fmt.Println("Creating instance for the first time!") lazySingletonInstance = &LazySingleton{} } return lazySingletonInstance}
// Instance is created only when first accessed// 2. Lazy Initialization On-Demanduse std::sync::OnceLock;struct Config { database_url: String,}static CONFIG: OnceLock<Config> = OnceLock::new();fn config() -> &'static Config { CONFIG.get_or_init(|| Config { database_url: "postgres://localhost".into(), })}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:
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._instancepublic class ThreadSafeSingleton { private static volatile ThreadSafeSingleton instance; private static final Object lock = new Object();
private ThreadSafeSingleton() { // Private constructor }
public static ThreadSafeSingleton getInstance() { if (instance == null) { synchronized (lock) { // Double-check after acquiring lock if (instance == null) { instance = new ThreadSafeSingleton(); } } } return instance; }}class ThreadSafeSingleton { /** Thread-safe singleton using double-check locking (conceptual - JS is single-threaded) */
private static instance: ThreadSafeSingleton | null = null; private static lock: boolean = false;
private constructor() { // Private constructor }
public static async getInstance(): Promise<ThreadSafeSingleton> { if (ThreadSafeSingleton.instance === null) { // Wait if another async operation is creating the instance while (ThreadSafeSingleton.lock) { await new Promise(resolve => setTimeout(resolve, 10)); }
// Double-check after waiting if (ThreadSafeSingleton.instance === null) { ThreadSafeSingleton.lock = true; ThreadSafeSingleton.instance = new ThreadSafeSingleton(); ThreadSafeSingleton.lock = false; } } return ThreadSafeSingleton.instance; }}
// Note: JavaScript/TypeScript is single-threaded, but async operations can cause race conditions#include <mutex>
class ThreadSafeSingleton {private: static ThreadSafeSingleton* instance; static std::mutex mtx;
// Private constructor ThreadSafeSingleton() {}
// Delete copy constructor and assignment operator ThreadSafeSingleton(const ThreadSafeSingleton&) = delete; ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
public: static ThreadSafeSingleton* getInstance() { if (instance == nullptr) { std::lock_guard<std::mutex> lock(mtx); // Double-check after acquiring lock if (instance == nullptr) { instance = new ThreadSafeSingleton(); } } return instance; }};
// Initialize static membersThreadSafeSingleton* ThreadSafeSingleton::instance = nullptr;std::mutex ThreadSafeSingleton::mtx;public class ThreadSafeSingleton{ private static volatile ThreadSafeSingleton instance; private static readonly object lockObj = new object();
private ThreadSafeSingleton() { // Private constructor }
public static ThreadSafeSingleton GetInstance() { if (instance == null) { lock (lockObj) { // Double-check after acquiring lock if (instance == null) { instance = new ThreadSafeSingleton(); } } } return instance; }}package main
import "sync"
type ThreadSafeSingleton struct{}
var ( threadSafeSingleton *ThreadSafeSingleton threadSafeMu sync.Mutex)
func GetThreadSafeSingleton() *ThreadSafeSingleton { if threadSafeSingleton == nil { threadSafeMu.Lock() if threadSafeSingleton == nil { threadSafeSingleton = &ThreadSafeSingleton{} } threadSafeMu.Unlock() } return threadSafeSingleton}// 3. Thread-Safe Singleton Double-Check Lockinguse std::sync::OnceLock;struct Config { database_url: String,}static CONFIG: OnceLock<Config> = OnceLock::new();fn config() -> &'static Config { CONFIG.get_or_init(|| Config { database_url: "postgres://localhost".into(), })}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:
# singleton_module.pyclass _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
When to Use Singleton Pattern?
Section titled “When to Use Singleton Pattern?”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
When NOT to Use Singleton Pattern?
Section titled “When NOT to Use Singleton Pattern?”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
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Not Making Constructor Private
Section titled “Mistake 1: Not Making Constructor Private”# ❌ 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!// ❌ Bad: Constructor not private - can create multiple instances!public class BadSingleton { private static BadSingleton instance;
public BadSingleton() { // Public constructor! if (instance == null) { instance = this; } else { throw new RuntimeException("Singleton already exists!"); } }
public static BadSingleton getInstance() { if (instance == null) { instance = new BadSingleton(); } return instance; }}
// Problem: Can still create instances directly!BadSingleton obj1 = new BadSingleton(); // Works!BadSingleton obj2 = new BadSingleton(); // Throws exception, but obj1 and obj2 are different!// ❌ Bad: Constructor not private - can create multiple instances!class BadSingleton { private static instance: BadSingleton | null = null;
constructor() { // Public constructor! if (BadSingleton.instance === null) { BadSingleton.instance = this; } else { throw new Error("Singleton already exists!"); } }
static getInstance(): BadSingleton { if (BadSingleton.instance === null) { BadSingleton.instance = new BadSingleton(); } return BadSingleton.instance; }}
// Problem: Can still create instances directly!const obj1 = new BadSingleton(); // Works!// const obj2 = new BadSingleton(); // Throws exception, but obj1 and obj2 are different!// ❌ Bad: Constructor not private - can create multiple instances!class BadSingleton {private: static BadSingleton* instance;
public: BadSingleton() { // Public constructor! if (instance == nullptr) { instance = this; } else { throw std::runtime_error("Singleton already exists!"); } }
static BadSingleton* getInstance() { if (instance == nullptr) { instance = new BadSingleton(); } return instance; }};
BadSingleton* BadSingleton::instance = nullptr;
// Problem: Can still create instances directly!// BadSingleton obj1; // Works!// BadSingleton obj2; // Throws exception, but obj1 and obj2 are different!// ❌ Bad: Constructor not private - can create multiple instances!public class BadSingleton{ private static BadSingleton instance;
public BadSingleton() // Public constructor! { if (instance == null) { instance = this; } else { throw new Exception("Singleton already exists!"); } }
public static BadSingleton GetInstance() { if (instance == null) { instance = new BadSingleton(); } return instance; }}
// Problem: Can still create instances directly!// BadSingleton obj1 = new BadSingleton(); // Works!// BadSingleton obj2 = new BadSingleton(); // Throws exception, but obj1 and obj2 are different!package main
type BadSingleton struct{}
var badSingletonInstance *BadSingleton
func NewBadSingleton() *BadSingleton { if badSingletonInstance == nil { badSingletonInstance = &BadSingleton{} return badSingletonInstance } panic("Singleton already exists!")}
func GetBadSingleton() *BadSingleton { if badSingletonInstance == nil { badSingletonInstance = NewBadSingleton() } return badSingletonInstance}
// Problem: Package can still expose NewBadSingleton — callers may create conflicting instances!// BadSingleton{} would not run this logic; mirror C#'s "broken" pattern with exported New only.// Mistake 1: Not Making Constructor Privatestruct Config { database_url: String,}fn load_config_everywhere() -> Config { Config { database_url: "postgres://localhost".into(), }}Mistake 2: Not Thread-Safe in Multi-Threaded Environment
Section titled “Mistake 2: Not Thread-Safe in Multi-Threaded Environment”# ❌ 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!// ❌ Bad: Not thread-safe - can create multiple instances!public class NotThreadSafeSingleton { private static NotThreadSafeSingleton instance;
public static NotThreadSafeSingleton getInstance() { if (instance == null) { // Race condition: Multiple threads can pass this check! instance = new NotThreadSafeSingleton(); } return instance; }}
// In multi-threaded environment, multiple instances can be created!// ❌ Bad: Not thread-safe - can create multiple instances!class NotThreadSafeSingleton { private static instance: NotThreadSafeSingleton | null = null;
private constructor() {}
public static getInstance(): NotThreadSafeSingleton { if (NotThreadSafeSingleton.instance === null) { // Race condition: Multiple async operations can pass this check! NotThreadSafeSingleton.instance = new NotThreadSafeSingleton(); } return NotThreadSafeSingleton.instance; }}
// In multi-threaded/async environment, multiple instances can be created!// ❌ Bad: Not thread-safe - can create multiple instances!class NotThreadSafeSingleton {private: static NotThreadSafeSingleton* instance;
NotThreadSafeSingleton() {}
public: static NotThreadSafeSingleton* getInstance() { if (instance == nullptr) { // Race condition: Multiple threads can pass this check! instance = new NotThreadSafeSingleton(); } return instance; }};
NotThreadSafeSingleton* NotThreadSafeSingleton::instance = nullptr;
// In multi-threaded environment, multiple instances can be created!// ❌ Bad: Not thread-safe - can create multiple instances!public class NotThreadSafeSingleton{ private static NotThreadSafeSingleton instance;
private NotThreadSafeSingleton() {}
public static NotThreadSafeSingleton GetInstance() { if (instance == null) { // Race condition: Multiple threads can pass this check! instance = new NotThreadSafeSingleton(); } return instance; }}
// In multi-threaded environment, multiple instances can be created!package main
type NotThreadSafeSingleton struct{}
var notThreadSafeInst *NotThreadSafeSingleton
func GetNotThreadSafeSingleton() *NotThreadSafeSingleton { if notThreadSafeInst == nil { // Race condition: Multiple goroutines can pass this check! notThreadSafeInst = &NotThreadSafeSingleton{} } return notThreadSafeInst}
// In multi-threaded environment, multiple instances can be created!// Mistake 2: Not Thread-Safe in Multi-Threaded Environmentstruct Config { database_url: String,}fn load_config_everywhere() -> Config { Config { database_url: "postgres://localhost".into(), }}Mistake 3: Serializable Singleton Without readResolve
Section titled “Mistake 3: Serializable Singleton Without readResolve”import java.io.Serializable;
// ❌ Bad: Serializable singleton without readResolvepublic 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 readResolvepublic 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(); }}Mistake 4: Using Singleton for Everything
Section titled “Mistake 4: Using Singleton for Everything”# ❌ 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 classclass User: def __init__(self, name, email): self.name = name self.email = email// ❌ Bad: Using Singleton for everything!public class UserSingleton { private static UserSingleton instance;
private UserSingleton() {}
public static UserSingleton getInstance() { if (instance == null) { instance = new UserSingleton(); } return instance; }
private String name; private String email;}
// Problem: What if you need multiple users? Singleton prevents that!// ✅ Better: Use regular classpublic class User { private String name; private String email;
public User(String name, String email) { this.name = name; this.email = email; }}// ❌ Bad: Using Singleton for everything!class UserSingleton { private static instance: UserSingleton | null = null; name?: string; email?: string;
private constructor() {}
static getInstance(): UserSingleton { if (UserSingleton.instance === null) { UserSingleton.instance = new UserSingleton(); } return UserSingleton.instance; }}
// Problem: What if you need multiple users? Singleton prevents that!// ✅ Better: Use regular classclass User { constructor(public name: string, public email: string) {}}// ❌ Bad: Using Singleton for everything!class UserSingleton {private: static UserSingleton* instance; std::string name; std::string email;
UserSingleton() {}
public: static UserSingleton* getInstance() { if (instance == nullptr) { instance = new UserSingleton(); } return instance; }};
UserSingleton* UserSingleton::instance = nullptr;
// Problem: What if you need multiple users? Singleton prevents that!// ✅ Better: Use regular classclass User {public: std::string name; std::string email;
User(const std::string& name, const std::string& email) : name(name), email(email) {}};// ❌ Bad: Using Singleton for everything!public class UserSingleton{ private static UserSingleton instance;
private UserSingleton() {}
public static UserSingleton GetInstance() { if (instance == null) { instance = new UserSingleton(); } return instance; }
public string Name { get; set; } public string Email { get; set; }}
// Problem: What if you need multiple users? Singleton prevents that!// ✅ Better: Use regular classpublic class User{ public string Name { get; set; } public string Email { get; set; }
public User(string name, string email) { Name = name; Email = email; }}package main
// ❌ Bad: Using Singleton for everything!type UserSingleton struct { Name string Email string}
var userSingletonInst *UserSingleton
func GetUserSingleton() *UserSingleton { if userSingletonInst == nil { userSingletonInst = &UserSingleton{} } return userSingletonInst}
// Problem: What if you need multiple users? Singleton prevents that!// ✅ Better: Use regular struct + constructortype User struct { Name string Email string}
func NewUser(name, email string) *User { return &User{Name: name, Email: email}}// Mistake 4: Using Singleton for Everythingstruct Config { database_url: String,}fn load_config_everywhere() -> Config { Config { database_url: "postgres://localhost".into(), }}Benefits of Singleton Pattern
Section titled “Benefits of Singleton Pattern”- Controlled Access - Only one instance exists, controlled access point
- Resource Efficiency - Expensive objects created only once
- Global State - Single source of truth for shared state
- Memory Efficient - No memory waste from multiple instances
- Lazy Initialization - Can defer expensive initialization until needed
- Namespace - Provides a namespace for global variables
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Singleton Pattern?
Section titled “What is Singleton Pattern?”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.
Why Use It?
Section titled “Why Use It?”- ✅ 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
How It Works?
Section titled “How It Works?”- Private constructor - Prevents external instantiation
- Static instance variable - Stores the single instance
- Static getter method - Returns the instance (creates if needed)
- Controlled creation - Only one instance can exist
Key Components
Section titled “Key Components”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
Simple Example
Section titled “Simple Example”class Singleton: _instance = None
def __new__(cls): if cls._instance is None: cls._instance = super(Singleton, cls).__new__(cls) return cls._instance
# Usageobj1 = Singleton()obj2 = Singleton()print(obj1 is obj2) # True - same instance!class Singleton { private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; }}
// UsageSingleton obj1 = Singleton.getInstance();Singleton obj2 = Singleton.getInstance();// obj1 == obj2 - same instance!class Singleton { private static instance: Singleton;
private constructor() {}
static getInstance(): Singleton { if (!Singleton.instance) Singleton.instance = new Singleton(); return Singleton.instance; }}
// Usageconst obj1 = Singleton.getInstance();const obj2 = Singleton.getInstance();// obj1 === obj2 - same instance!class Singleton { static Singleton* instance; Singleton() {}public: static Singleton* getInstance() { if (!instance) instance = new Singleton(); return instance; }};Singleton* Singleton::instance = nullptr;
// Usageauto* obj1 = Singleton::getInstance();auto* obj2 = Singleton::getInstance();// obj1 == obj2 - same instance!class Singleton { private static Singleton instance;
private Singleton() {}
public static Singleton GetInstance() { if (instance == null) instance = new Singleton(); return instance; }}
// Usagevar obj1 = Singleton.GetInstance();var obj2 = Singleton.GetInstance();// obj1 == obj2 - same instance!type Singleton struct{}
var inst *Singleton
func GetInstance() *Singleton { if inst == nil { inst = &Singleton{} } return inst}
// Usageobj1 := GetInstance()obj2 := GetInstance()// obj1 == obj2 — same instance!// Simple Exampleuse std::sync::OnceLock;struct Config { database_url: String,}static CONFIG: OnceLock<Config> = OnceLock::new();fn config() -> &'static Config { CONFIG.get_or_init(|| Config { database_url: "postgres://localhost".into(), })}When to Use?
Section titled “When to Use?”✅ Exactly one instance needed
✅ Expensive resource creation
✅ Global access required
✅ Shared state management
✅ Configuration management
✅ Logging systems
When NOT to Use?
Section titled “When NOT to Use?”❌ Multiple instances might be needed
❌ Testing difficulties are a concern
❌ Hidden dependencies are problematic
❌ Distributed systems
❌ Violates Single Responsibility
Key Takeaways
Section titled “Key Takeaways”- 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
Common Pattern Structure
Section titled “Common Pattern Structure”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()
# Usageinstance = Singleton.get_instance()class Singleton { private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; }}
// UsageSingleton instance = Singleton.getInstance();class Singleton { private static instance: Singleton;
private constructor() {}
static getInstance(): Singleton { if (!Singleton.instance) Singleton.instance = new Singleton(); return Singleton.instance; }}
// Usageconst instance = Singleton.getInstance();class Singleton { static Singleton* instance; Singleton() {}public: static Singleton* getInstance() { if (!instance) instance = new Singleton(); return instance; }};Singleton* Singleton::instance = nullptr;
// UsageSingleton* instance = Singleton::getInstance();class Singleton { private static Singleton instance;
private Singleton() {}
public static Singleton GetInstance() { if (instance == null) instance = new Singleton(); return instance; }}
// Usagevar instance = Singleton.GetInstance();type Singleton struct{}
var singletonInst *Singleton
func GetInstance() *Singleton { if singletonInst == nil { singletonInst = &Singleton{} } return singletonInst}
// Usageinstance := GetInstance()// Common Pattern Structureuse std::sync::OnceLock;struct Config { database_url: String,}static CONFIG: OnceLock<Config> = OnceLock::new();fn config() -> &'static Config { CONFIG.get_or_init(|| Config { database_url: "postgres://localhost".into(), })}Remember
Section titled “Remember”- 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
Interview Focus: Singleton Pattern
Section titled “Interview Focus: Singleton Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”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
2. When to Use Singleton Pattern
Section titled “2. When to Use Singleton Pattern”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.”
3. Structure and Components
Section titled “3. Structure and Components”Must explain:
- Private Constructor - Prevents external instantiation
- Static Instance Variable - Stores the single instance
- Static Getter Method - Returns the instance (creates if needed)
- Controlled Creation - Ensures only one instance exists
Visual explanation:
Client → getInstance() → Singleton Instance (created once, reused always)4. Thread Safety
Section titled “4. Thread Safety”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.”
5. Benefits and Trade-offs
Section titled “5. Benefits and Trade-offs”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
6. Common Interview Questions
Section titled “6. Common Interview Questions”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.”
7. Implementation Details
Section titled “7. Implementation Details”Key implementation points:
- Private Constructor - Prevents external instantiation
def __new__(cls): if cls._instance is None: cls._instance = super(Singleton, cls).__new__(cls) return cls._instanceprivate Singleton() {} // Private constructor prevents external creationprivate constructor() {} // Private constructor prevents external creationSingleton() {} // Private constructor prevents external creationprivate Singleton() {} // Private constructor prevents external creation// Unexported helpers / lowercase types act like a private constructor:// only types and funcs in this package can allocate the singleton holder.type singleton struct{}// 7. Implementation Detailsuse std::sync::OnceLock;struct Config { database_url: String,}static CONFIG: OnceLock<Config> = OnceLock::new();fn config() -> &'static Config { CONFIG.get_or_init(|| Config { database_url: "postgres://localhost".into(), })}- 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)public static synchronized Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance;}// TypeScript is single-threaded; use locks if using Web Workersstatic std::mutex mtx;std::lock_guard<std::mutex> lock(mtx);if (!instance) instance = new Singleton();private static readonly object _lock = new object();lock (_lock) { if (instance == null) instance = new Singleton();}import "sync"
var mu sync.Mutex
// ...
mu.Lock()defer mu.Unlock()if instance == nil { instance = new(Singleton)}// 7. Implementation Detailsuse std::sync::OnceLock;struct Config { database_url: String,}static CONFIG: OnceLock<Config> = OnceLock::new();fn config() -> &'static Config { CONFIG.get_or_init(|| Config { database_url: "postgres://localhost".into(), })}-
Lazy vs Eager Initialization - Choose based on needs
- Lazy: Create when first accessed
- Eager: Create when class is loaded
-
Serialization (Java) - Implement
readResolve()to prevent new instances
8. Real-World Examples
Section titled “8. Real-World Examples”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
9. Common Mistakes to Avoid
Section titled “9. Common Mistakes to Avoid”Mistakes interviewers watch for:
-
Not Thread-Safe - Can create multiple instances
- ❌ Bad: No synchronization in multi-threaded environment
- ✅ Good: Use double-check locking or synchronized methods
-
Public Constructor - Allows external instantiation
- ❌ Bad: Public constructor
- ✅ Good: Private constructor
-
Serialization Issues (Java) - Deserialization creates new instance
- ❌ Bad: Serializable without
readResolve() - ✅ Good: Implement
readResolve()method
- ❌ Bad: Serializable without
-
Overuse - Using Singleton for everything
- ❌ Bad: Singleton for classes that need multiple instances
- ✅ Good: Use Singleton only when exactly one instance is needed
10. Alternatives to Singleton
Section titled “10. Alternatives to Singleton”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.”
Interview Checklist
Section titled “Interview Checklist”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! 🎯