Singleton
Ensures that a class has only one instance and provides a global point of access to it.
Singleton Pattern: A Complete Guide with Examples in 11 Programming Languages
The Singleton Pattern is one of the most well-known (and controversial) creational design patterns that ensures a class has only one instance and provides a global point of access to it. Despite its simplicity, implementing Singleton correctly requires careful consideration of thread safety, lazy initialization, and language-specific idioms. Whether you're managing database connections, logging systems, or configuration managers, understanding when and how to use Singleton is crucial for building robust applications.
In this comprehensive guide, we'll explore the Singleton Pattern with real-world examples in Python, TypeScript, Java, JavaScript, C#, PHP, Go, Rust, Dart, Swift, and Kotlin - complete with language-specific best practices, common pitfalls, and when NOT to use this pattern.
Table of Contents
- What is the Singleton Pattern?
- Why Use the Singleton Pattern?
- Singleton Pattern Comparison
- Singleton Pattern Explained
- Class Diagram
- Beginner-Friendly Example
- Production-Ready Example
- Real-World Use Cases
- Language-Specific Mistakes and Anti-Patterns
- Frequently Asked Questions
- Key Takeaways
What is the Singleton Pattern?
The Singleton Pattern restricts the instantiation of a class to a single instance and provides a global access point to that instance. It's like having only one president of a country at any given time - there can only be one, and everyone knows how to reach them.
The pattern ensures that:
- A class has only one instance
- The instance is globally accessible
- The instance is created lazily (when first needed) or eagerly (at startup)
Why Use the Singleton Pattern?
The Singleton Pattern offers specific benefits in certain scenarios:
- Controlled Access: Single instance ensures controlled access to shared resources
- Memory Efficiency: Only one instance exists throughout the application lifecycle
- Global State: Provides a global point of access without using global variables
- Lazy Initialization: Instance can be created only when needed
- Resource Management: Manages expensive resources like database connections or thread pools
However: Singleton is often overused and can introduce hidden dependencies and testing challenges. Use it judiciously.
Singleton Pattern Comparison
Let's compare Singleton with related concepts:
| Concept | Instances | Scope | Thread Safety Concern | Use Case |
|---|---|---|---|---|
| Singleton | Exactly one | Global | Critical | Configuration, logging, caching |
| Static Class | None (just static methods) | Global | Methods must be thread-safe | Utility functions, helpers |
| Dependency Injection | Controlled by container | Scoped/Singleton/Transient | Container manages | Preferred in modern apps |
| Global Variable | One | Global | No protection | Legacy code (avoid) |
| Multiton | One per key | Global | Critical | Multiple named instances |
Key Distinction: Singleton guarantees one instance with controlled creation, while static classes have no instances.
Singleton Pattern Explained
The Singleton Pattern typically involves:
Core Components:
- Private Constructor: Prevents external instantiation
- Static Instance Variable: Holds the single instance
- Static Access Method: Provides global access point
- Thread Safety Mechanism: Ensures safe creation in multi-threaded environments
Common Variations:
- Eager Initialization: Instance created at class loading
- Lazy Initialization: Instance created on first access
- Thread-Safe Lazy: Lazy initialization with synchronization
- Double-Checked Locking: Optimized thread-safe lazy initialization
- Bill Pugh (Initialization-on-demand): Using inner static class
- Enum Singleton: Using enum (Java-specific, most robust)
Class Diagram
Here's the UML class diagram showing the Singleton structure:
Beginner-Friendly Example
Let's start with a simple example: a configuration manager that loads application settings once and provides global access.
import threading
class ConfigurationManager:
"""Thread-safe Singleton configuration manager"""
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
# Double-checked locking
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
# Ensure initialization happens only once
if self._initialized:
return
self._initialized = True
self._config = {
'app_name': 'MyApp',
'version': '1.0.0',
'debug': False
}
print("ConfigurationManager initialized")
def get(self, key: str, default=None):
return self._config.get(key, default)
def set(self, key: str, value):
self._config[key] = value
# Usage
if __name__ == "__main__":
# Both variables reference the same instance
config1 = ConfigurationManager()
config2 = ConfigurationManager()
print(f"Same instance? {config1 is config2}") # True
config1.set('debug', True)
print(f"Debug from config2: {config2.get('debug')}") # True
Production-Ready Example
Now let's look at a more realistic, production-ready implementation: a database connection pool manager with thread safety, lazy initialization, proper cleanup, and comprehensive error handling.
š Python (Production Example)
import threading
import logging
from typing import Optional, Dict, Any
from contextlib import contextmanager
from datetime import datetime
import time
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class DatabaseConnectionPool:
"""
Thread-safe Singleton database connection pool.
Manages a pool of database connections for efficient resource usage.
"""
_instance: Optional['DatabaseConnectionPool'] = None
_lock = threading.Lock()
_initialized = False
def __new__(cls, *args, **kwargs):
"""Thread-safe singleton implementation"""
if cls._instance is None:
with cls._lock:
# Double-checked locking
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(
self,
host: str = "localhost",
port: int = 5432,
database: str = "mydb",
user: str = "admin",
password: str = "password",
min_connections: int = 5,
max_connections: int = 20
):
"""Initialize the connection pool (only once)"""
# Prevent re-initialization
if self._initialized:
return
with self._lock:
if self._initialized:
return
self.host = host
self.port = port
self.database = database
self.user = user
self.password = password
self.min_connections = min_connections
self.max_connections = max_connections
# Connection pool state
self._available_connections = []
self._in_use_connections = set()
self._total_connections = 0
self._pool_lock = threading.Lock()
# Statistics
self._stats = {
'connections_created': 0,
'connections_closed': 0,
'total_requests': 0,
'active_connections': 0
}
# Initialize minimum connections
self._initialize_pool()
self._initialized = True
logger.info(
f"DatabaseConnectionPool initialized: "
f"{self.host}:{self.port}/{self.database} "
f"(min={self.min_connections}, max={self.max_connections})"
)
def _initialize_pool(self):
"""Create minimum number of connections"""
for _ in range(self.min_connections):
conn = self._create_connection()
if conn:
self._available_connections.append(conn)
def _create_connection(self) -> Optional[Dict[str, Any]]:
"""Create a new database connection"""
try:
# Simulate connection creation
connection = {
'id': f"conn_{self._total_connections}",
'created_at': datetime.now(),
'last_used': datetime.now(),
'query_count': 0,
'status': 'active'
}
self._total_connections += 1
self._stats['connections_created'] += 1
logger.debug(f"Created connection: {connection['id']}")
return connection
except Exception as e:
logger.error(f"Failed to create connection: {e}")
return None
def get_connection(self, timeout: float = 5.0) -> Optional[Dict[str, Any]]:
"""
Get a connection from the pool.
Args:
timeout: Maximum time to wait for a connection
Returns:
Connection object or None if timeout
"""
start_time = time.time()
with self._pool_lock:
self._stats['total_requests'] += 1
# Try to get available connection
if self._available_connections:
conn = self._available_connections.pop()
self._in_use_connections.add(conn['id'])
conn['last_used'] = datetime.now()
self._stats['active_connections'] = len(self._in_use_connections)
logger.debug(f"Reusing connection: {conn['id']}")
return conn
# Create new connection if under max limit
if self._total_connections < self.max_connections:
conn = self._create_connection()
if conn:
self._in_use_connections.add(conn['id'])
self._stats['active_connections'] = len(self._in_use_connections)
return conn
# Wait for connection to become available
while time.time() - start_time < timeout:
time.sleep(0.1)
with self._pool_lock:
if self._available_connections:
conn = self._available_connections.pop()
self._in_use_connections.add(conn['id'])
conn['last_used'] = datetime.now()
self._stats['active_connections'] = len(self._in_use_connections)
return conn
logger.warning("Connection pool timeout - no connections available")
return None
def release_connection(self, connection: Dict[str, Any]):
"""Return a connection to the pool"""
with self._pool_lock:
if connection['id'] in self._in_use_connections:
self._in_use_connections.remove(connection['id'])
connection['last_used'] = datetime.now()
self._available_connections.append(connection)
self._stats['active_connections'] = len(self._in_use_connections)
logger.debug(f"Released connection: {connection['id']}")
else:
logger.warning(f"Attempted to release unknown connection: {connection['id']}")
@contextmanager
def connection(self):
"""
Context manager for automatic connection management.
Usage:
with pool.connection() as conn:
# Use connection
pass
"""
conn = self.get_connection()
if conn is None:
raise RuntimeError("Failed to acquire database connection")
try:
yield conn
finally:
self.release_connection(conn)
def execute_query(self, query: str, params: tuple = ()) -> Dict[str, Any]:
"""
Execute a query using a connection from the pool.
Args:
query: SQL query to execute
params: Query parameters
Returns:
Query result
"""
with self.connection() as conn:
# Simulate query execution
conn['query_count'] += 1
result = {
'query': query,
'params': params,
'executed_at': datetime.now(),
'connection_id': conn['id'],
'rows_affected': 1
}
logger.info(f"Executed query on {conn['id']}: {query[:50]}...")
return result
def get_stats(self) -> Dict[str, Any]:
"""Get connection pool statistics"""
with self._pool_lock:
stats = self._stats.copy()
stats['available_connections'] = len(self._available_connections)
stats['in_use_connections'] = len(self._in_use_connections)
stats['total_connections'] = self._total_connections
return stats
def shutdown(self):
"""Close all connections and cleanup resources"""
with self._pool_lock:
logger.info("Shutting down connection pool...")
# Close all connections
all_connections = self._available_connections + list(self._in_use_connections)
for conn in all_connections:
conn['status'] = 'closed'
self._stats['connections_closed'] += 1
self._available_connections.clear()
self._in_use_connections.clear()
logger.info(f"Connection pool shutdown complete. Stats: {self._stats}")
def __repr__(self) -> str:
return (
f"DatabaseConnectionPool("
f"host={self.host}, "
f"port={self.port}, "
f"database={self.database}, "
f"total={self._total_connections}, "
f"available={len(self._available_connections)}, "
f"in_use={len(self._in_use_connections)}"
f")"
)
# Usage Example
def simulate_database_operations():
"""Simulate concurrent database operations"""
# Get singleton instance
pool = DatabaseConnectionPool(
host="db.example.com",
database="production",
min_connections=3,
max_connections=10
)
# Verify singleton
pool2 = DatabaseConnectionPool()
print(f"Singleton verified: {pool is pool2}")
print(f"Pool info: {pool}\n")
# Simulate some queries
print("="*60)
print("Executing Queries")
print("="*60)
# Example 1: Using context manager
with pool.connection() as conn:
print(f"Got connection: {conn['id']}")
time.sleep(0.1) # Simulate work
# Example 2: Direct query execution
result = pool.execute_query("SELECT * FROM users WHERE id = ?", (123,))
print(f"Query result: {result['connection_id']}")
# Example 3: Multiple concurrent operations
def worker(worker_id: int):
for i in range(3):
result = pool.execute_query(
f"SELECT * FROM data WHERE worker_id = {worker_id}",
()
)
time.sleep(0.05)
threads = []
for i in range(5):
t = threading.Thread(target=worker, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join()
# Show statistics
print("\n" + "="*60)
print("Connection Pool Statistics")
print("="*60)
stats = pool.get_stats()
for key, value in stats.items():
print(f"{key}: {value}")
# Cleanup
pool.shutdown()
if __name__ == "__main__":
simulate_database_operations()
Output:
INFO:__main__:DatabaseConnectionPool initialized: db.example.com:5432/production (min=3, max=10)
Singleton verified: True
Pool info: DatabaseConnectionPool(host=db.example.com, port=5432, database=production, total=3, available=3, in_use=0)
============================================================
Executing Queries
============================================================
Got connection: conn_0
INFO:__main__:Executed query on conn_1: SELECT * FROM users WHERE id = ?...
Query result: conn_1
INFO:__main__:Executed query on conn_2: SELECT * FROM data WHERE worker_id = 0...
...
============================================================
Connection Pool Statistics
============================================================
connections_created: 5
connections_closed: 5
total_requests: 18
active_connections: 0
available_connections: 5
in_use_connections: 0
total_connections: 5
INFO:__main__:Shutting down connection pool...
INFO:__main__:Connection pool shutdown complete. Stats: {...}
Key Production Features:
- Thread Safety: Double-checked locking prevents race conditions
- Connection Pooling: Efficient reuse of expensive database connections
- Resource Management: Context manager ensures proper cleanup
- Statistics Tracking: Monitors pool health and usage
- Timeout Handling: Prevents indefinite blocking
- Lazy Initialization: Connections created only when needed
- Proper Cleanup: Shutdown method releases all resources
- Comprehensive Logging: Detailed logging for debugging
Real-World Use Cases
The Singleton Pattern is appropriate in specific scenarios where a single instance makes sense:
1. Configuration Management
Centralized application configuration:
ConfigurationManager ā Load once, access globally
Examples: Application settings, feature flags, environment variables Industries: All applications, microservices, mobile apps
2. Logging Systems
Single logger instance for consistent logging:
Logger ā One instance, multiple writers, thread-safe
Examples: Log4j, Winston, Python logging, Serilog Industries: All software development, monitoring, debugging
3. Database Connection Pools
Manage expensive database connections:
ConnectionPool ā Reuse connections, control max connections
Examples: HikariCP, c3p0, Apache Commons DBCP Industries: Web applications, enterprise software, APIs
4. Cache Managers
In-memory caching for performance:
CacheManager ā Single cache instance, shared across app
Examples: Redis clients, Memcached clients, in-memory caches Industries: High-performance applications, APIs, web services
5. Thread Pools
Manage worker threads efficiently:
ThreadPoolExecutor ā Fixed number of threads, task queue
Examples: Java ExecutorService, Python ThreadPoolExecutor Industries: Concurrent applications, servers, background processing
6. Hardware Interface Managers
Control access to hardware resources:
PrinterSpooler ā One spooler manages all print jobs
DeviceManager ā Single point of hardware access
Examples: Printer spoolers, device drivers, hardware APIs Industries: Operating systems, embedded systems, IoT
7. Application State
Global application state management:
ApplicationContext ā Current user, app state, session info
Examples: Spring ApplicationContext, Android Application class Industries: Enterprise applications, mobile apps, frameworks
8. Service Locators (though DI is preferred)
Central registry for services:
ServiceLocator ā Register and resolve services
Examples: Legacy applications, game engines Industries: Enterprise applications (legacy), game development
Language-Specific Mistakes and Anti-Patterns
Different programming languages have unique pitfalls when implementing Singleton. Let's explore common mistakes:
ā Mistake #1: Not Thread-Safe Singleton
# BAD: Race condition in multi-threaded environment
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls) # Race condition!
return cls._instance
# GOOD: Thread-safe with lock
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None: # Double-check
cls._instance = super().__new__(cls)
return cls._instance
ā Mistake #2: Multiple Initializations
# BAD: __init__ called every time
class Singleton:
def __init__(self):
self.data = [] # Resets every getInstance call!
# GOOD: Guard against re-initialization
class Singleton:
_initialized = False
def __init__(self):
if self._initialized:
return
self._initialized = True
self.data = []
ā Mistake #3: Not Preventing Copying
# BAD: Can create copies
import copy
singleton = Singleton()
duplicate = copy.deepcopy(singleton) # Oops, two instances!
# GOOD: Prevent copying
class Singleton:
def __deepcopy__(self, memo):
return self
def __copy__(self):
return self
Frequently Asked Questions
Q1: When should I NOT use Singleton?
Avoid Singleton when:
- You need testability (Singleton creates hidden dependencies)
- Your "singleton" has mutable state shared across the app (race conditions)
- You're using it just to avoid passing dependencies (use DI instead)
- The object's lifecycle should be managed by a framework
- You need different instances in different contexts (tests vs production)
Modern alternative: Use Dependency Injection with scoped lifetime management.
Q2: Is Singleton an anti-pattern?
It depends on the context:
Legitimate uses:
- Logger instances (truly global, stateless operations)
- Hardware interface managers (one printer spooler)
- Configuration that never changes after initialization
Anti-pattern when:
- Used for convenience instead of proper dependency injection
- Carries mutable global state
- Makes testing difficult
- Hides dependencies
Modern view: Singleton itself isn't bad, but global mutable state is. Prefer dependency injection with singleton scope.
Q3: How do I test code that uses Singleton?
Strategy 1: Dependency Injection
# Instead of
class Service:
def __init__(self):
self.logger = Logger.get_instance() # Hard dependency
# Do this
class Service:
def __init__(self, logger: Logger):
self.logger = logger # Injected dependency
# Test
def test_service():
mock_logger = MockLogger()
service = Service(mock_logger)
# Easy to test!
Strategy 2: Reset Method (use cautiously)
class Singleton:
_instance = None
@classmethod
def reset(cls):
"""For testing only!"""
cls._instance = None
# Test
def test_something():
Singleton.reset() # Fresh instance
singleton = Singleton()
# Test...
Strategy 3: Mock the Singleton
# Use unittest.mock or similar
from unittest.mock import patch
def test_with_mock():
with patch('module.Singleton.get_instance') as mock:
mock.return_value = MockSingleton()
# Test code that uses Singleton
Q4: How does Singleton work with Dependency Injection?
They can work together! The DI container manages the singleton:
# Instead of manual singleton
class Logger:
_instance = None
@staticmethod
def get_instance():
# ...
# Use DI container
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
logger = providers.Singleton(Logger) # Container manages lifecycle
# Usage
container = Container()
logger1 = container.logger()
logger2 = container.logger()
# Same instance, but managed by DI container
Benefits:
- Testability (can override in tests)
- No global state
- Clear dependencies
- Framework manages lifecycle
Q5: What's the difference between Singleton and Static Class?
| Aspect | Singleton | Static Class |
|---|---|---|
| Instances | One instance | No instances |
| State | Can have instance state | Only static state |
| Inheritance | Can implement interfaces, inherit | Cannot inherit or implement interfaces |
| Polymorphism | Supports polymorphism | No polymorphism |
| Lazy Loading | Can lazy load | Loaded when first accessed |
| Best for | Managing resources, stateful | Utility functions, stateless |
Example:
# Singleton (can implement interface)
class Logger(LoggerInterface):
_instance = None
def log(self, msg): ...
# Static Class (just functions)
class MathUtils:
@staticmethod
def add(a, b):
return a + b
Q6: How do I implement a multiton (multiple named singletons)?
Multiton allows multiple singleton instances, one per key:
class DatabaseConnection:
_instances = {}
_lock = threading.Lock()
def __new__(cls, database_name: str):
if database_name not in cls._instances:
with cls._lock:
if database_name not in cls._instances:
instance = super().__new__(cls)
cls._instances[database_name] = instance
return cls._instances[database_name]
# Usage
db1 = DatabaseConnection("users")
db2 = DatabaseConnection("users") # Same instance
db3 = DatabaseConnection("orders") # Different instance
print(db1 is db2) # True
print(db1 is db3) # False
Q7: Is Singleton thread-safe by default?
No! Thread safety must be explicitly implemented:
| Language | Default Thread Safety | How to Make Thread-Safe |
|---|---|---|
| Python | No | Use threading.Lock() with double-checked locking |
| Java | Depends | Use enum, or synchronized, or Bill Pugh pattern |
| C# | No | Use Lazy<T> or lock |
| JavaScript | Single-threaded | N/A (but consider async issues) |
| Go | No | Use sync.Once |
| Rust | No | Use Once or lazy_static |
| Kotlin | Yes (with object) | object keyword is thread-safe |
Q8: How do I handle Singleton cleanup/disposal?
Strategy 1: Explicit cleanup method
class ResourceManager:
def cleanup(self):
# Close connections, release resources
pass
# Call at app shutdown
resource_manager.cleanup()
Strategy 2: Context manager (Python)
class ResourceManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.cleanup()
with ResourceManager.get_instance() as rm:
# Use resource manager
pass
# Automatic cleanup
Strategy 3: IDisposable (C#)
public sealed class ResourceManager : IDisposable
{
public void Dispose()
{
// Cleanup logic
}
}
using (var rm = ResourceManager.Instance)
{
// Use resource manager
} // Automatic disposal
Q9: Can Singleton be inherited?
Generally, no - and you shouldn't want to:
# BAD: Inheriting singleton breaks the pattern
class BaseSingleton:
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
class DerivedSingleton(BaseSingleton):
pass
# Now you have two singletons!
base = BaseSingleton.get_instance()
derived = DerivedSingleton.get_instance()
If you need variation: Use Strategy or Factory patterns instead.
Q10: How does Singleton affect performance?
Performance considerations:
Pros:
- One-time initialization cost
- Memory efficient (one instance)
- Fast access (no creation overhead after first call)
Cons:
- Lazy initialization has small overhead (checking if instance exists)
- Thread synchronization adds overhead
- Global mutable state can cause cache invalidation
Optimization tips:
# Eager initialization for hot path code
class Logger:
_instance = Logger() # Created at import time
@staticmethod
def get_instance():
return Logger._instance # No check needed
Key Takeaways
Let's wrap up what we've learned about the Singleton Pattern across multiple programming languages:
šÆ Core Concept: Singleton ensures a class has only one instance and provides global access to it. Despite its simplicity, correct implementation requires attention to thread safety, initialization timing, and language-specific idioms.
š Key Characteristics:
- Single Instance: Only one object exists throughout the application
- Global Access: Accessible from anywhere in the codebase
- Controlled Creation: Private constructor prevents external instantiation
- Lazy or Eager: Instance created when needed or at startup
š Language-Specific Considerations:
- Python: Use
__new__with threading.Lock for thread safety - Java: Enum singleton is the most robust approach; Bill Pugh for lazy loading
- C#:
Lazy<T>provides thread-safe lazy initialization - JavaScript: Module pattern or class with static instance; single-threaded simplifies
- Kotlin:
objectkeyword provides built-in thread-safe singleton - Go:
sync.Onceguarantees single initialization - Rust:
lazy_staticcrate orOncewith unsafe code - Swift:
static letprovides thread-safe singleton - TypeScript: Private constructor with static instance
- PHP: Prevent cloning and unserialization
- Dart: Factory constructor for elegant singleton
š When to Use Singleton:
- Logging systems (one logger for the application)
- Configuration managers (load config once, use everywhere)
- Connection pools (manage expensive resources)
- Cache managers (shared cache across application)
- Hardware interface managers (one printer spooler)
- Thread pools (controlled concurrency)
ā ļø When NOT to Use Singleton:
- When you need testability (prefer dependency injection)
- For mutable global state (consider immutable state or DI)
- When object lifecycle should be managed by framework
- Just for convenience (passing dependencies is clearer)
- When different instances needed in different contexts
- When you're really just making a namespace for static methods
š ļø Best Practices Across All Languages:
- Thread Safety First: Always consider multi-threading implications
- Private Constructor: Prevent external instantiation
- Prevent Cloning: Block copy/clone/deserialization where applicable
- Lazy vs Eager: Choose based on initialization cost and usage pattern
- Immutable State: Prefer immutable configuration over mutable global state
- Document Intent: Clearly document why singleton is necessary
- Consider DI: Modern applications often prefer dependency injection
- Test Carefully: Provide reset mechanisms or use dependency injection for tests
āļø The Singleton Debate:
- Proponents say: Controlled access to shared resources, memory efficient, prevents multiple initializations
- Critics say: Global state, hidden dependencies, testing difficulties, violates Single Responsibility Principle
- Modern consensus: Use sparingly; prefer dependency injection with singleton scope
š” Remember:
- Singleton is about instance control, not global access
- Thread safety is not automatic in most languages
- Testing is harder with traditional singleton
- Dependency injection usually preferred in modern applications
- If you think you need a singleton, consider whether you really need shared mutable state (probably not)
š Alternatives to Consider:
- Dependency Injection: Let a DI container manage singleton lifecycle
- Monostate Pattern: Multiple instances, shared state
- Registry Pattern: Central registry for object lookup
- Service Locator: (though also problematic) Central service access
- Module Pattern (JavaScript): Encapsulation without class
The Singleton Pattern is one of the most controversial patterns in software design. While it has legitimate use cases (logging, configuration, resource pools), it's often overused. Modern applications increasingly favor dependency injection with managed lifetimes over hand-rolled singletons. When you do use Singleton, implement it correctly with proper thread safety and understand the trade-offs you're making.
Want to dive deeper into design patterns? Check out our comprehensive guides on:
- Factory Method Pattern
- Abstract Factory Pattern
- Builder Pattern
- Prototype Pattern
- Dependency Injection Patterns
Each guide includes examples in all 11 programming languages with language-specific best practices.