6 min read
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Singleton Pattern
1. What Is It?
The pattern ensures a class has exactly one instance throughout the lifetime of an application, and provides a single global access point to that instance. Without it, client code could accidentally create multiple independent instances of what should be a shared resource — imagine two separate logging systems each writing to the same file, or two cache managers each holding a divergent view of the data.
The pattern solves both problems in tandem: it prevents duplicate instantiation and centralizes access. The instance is typically created lazily (on first access) to avoid paying the initialization cost until it's actually needed.
Your application uses an in-memory cache manager to store frequently accessed database query results. A developer notices that different parts of the codebase are each instantiating their own cache manager object. What is the most likely consequence of this design?
2. How It Works
Step-by-step mechanics:
- Make the constructor
private(Java) or override the creation mechanism (Python). - Store the single instance in a
private staticfield on the class itself. - Expose a
public staticmethod (getInstance()/get_instance()) that creates the instance on first call and returns the cached one on every subsequent call. - In multi-threaded environments, guard the first-creation check with a lock to prevent two threads from each seeing "no instance yet" simultaneously.
Python
from __future__ import annotations import threading class SingletonMeta(type): """Thread-safe metaclass that enforces single-instance creation.""" _instances: dict[type, "SingletonMeta"] = {} _lock: threading.Lock = threading.Lock() def __call__(cls, *args, **kwargs): with cls._lock: if cls not in cls._instances: instance = super().__call__(*args, **kwargs) cls._instances[cls] = instance return cls._instances[cls] class DatabaseConnection(metaclass=SingletonMeta): """A database connection pool — exactly one should exist per process.""" def __init__(self, url: str) -> None: self.url = url self._connected = False def connect(self) -> None: if not self._connected: print(f"Connecting to {self.url}") self._connected = True def query(self, sql: str) -> list: print(f"Executing: {sql}") return [] # Usage db1 = DatabaseConnection("postgres://localhost/mydb") db2 = DatabaseConnection("postgres://localhost/mydb") assert db1 is db2 # True — same object
Java
public final class DatabaseConnection { // volatile ensures the write to _instance is visible across threads immediately private static volatile DatabaseConnection instance; private final String url; private boolean connected; private DatabaseConnection(String url) { this.url = url; } public static DatabaseConnection getInstance(String url) { if (instance == null) { // first check (no lock) synchronized (DatabaseConnection.class) { if (instance == null) { // second check (with lock) instance = new DatabaseConnection(url); } } } return instance; } public void connect() { if (!connected) { System.out.println("Connecting to " + url); connected = true; } } public void query(String sql) { System.out.println("Executing: " + sql); } } // Preferred modern Java form — enum-based Singleton public enum AppConfig { INSTANCE; private final String databaseUrl = System.getenv("DB_URL"); public String getDatabaseUrl() { return databaseUrl; } } // Usage AppConfig config = AppConfig.INSTANCE;
The Java
enumform is thread-safe by JVM specification, serialisation-safe without extra code, and immune to reflection-based instantiation attacks.
A backend service uses a Singleton database connection pool. During startup, two threads both call getInstance() at the same moment before any instance exists. Without any locking mechanism, what is the most likely risk?
3. Variants & Comparisons
| Approach | How It Works | Pros | Cons | Best For |
|---|---|---|---|---|
| Lazy initialization (basic) | Create on first getInstance() call | Simple; no upfront cost | Not thread-safe | Single-threaded apps |
| Double-checked locking | Null-check outside + inside synchronized | Thread-safe; low lock overhead | volatile required; verbose | Multi-threaded Java |
| Eager initialization | Create instance in static field initializer | Thread-safe by JVM; simple | Instance created even if never used | Cheap-to-create resources |
| Enum Singleton (Java) | JVM manages a single enum constant | Thread-safe; serialization-safe; reflection-safe | Java only; can feel unnatural | Java applications |
| Metaclass (Python) | Override type.__call__ to cache instances | Transparent to callers; inheritable | Metaclass conflicts are possible | Python applications |
| Module-level variable (Python) | Python module is imported once; store instance at module scope | Idiomatic Python; zero boilerplate | Slightly less encapsulated | Python scripts/services |
A Java backend service needs a singleton database connection pool that is accessed frequently from multiple threads. The connection pool is somewhat expensive to initialize, so you want to avoid creating it if it's never used. Which singleton implementation strategy best fits this scenario?
4. When to Use It (and When NOT To)
Use when:
- Exactly one shared resource must coordinate access across the system: logging, configuration, connection pools, device drivers, caches.
- You need a controlled access point, not just a convenient global.
Do NOT use when:
- You're reaching for it just to avoid passing a dependency — use instead.
- You need testability — Singletons are hard to replace with mocks; they carry global between tests.
- The "one instance" constraint is an assumption, not a hard requirement — requirements change (multi-tenant systems, test isolation).
Anti-patterns:
- Singleton as a service locator — hiding all dependencies behind a Singleton registry couples everything together invisibly.
- Mutable global — a Singleton holding mutable data creates hidden ; concurrent mutations require careful locking.
- Overuse — using Singleton for every utility class turns object-oriented code into disguised global state.
Decision triggers:
- "This resource must be initialized once and shared everywhere" → consider Singleton
- "I just want easy access to this object" → use instead
- "I need one instance per thread or per request" → use thread-local storage or scoped DI containers
A developer is building a web application and notices they need to access a UserService object in many different classes. To avoid passing it through multiple constructors, they decide to make UserService a Singleton. What is the main problem with this approach?
5. Real-World Usage
Java java.lang.Runtime
Runtime.getRuntime() returns the single Runtime instance representing the JVM process. There is exactly one runtime per JVM — the pattern enforces this constraint at the API level.
Spring Framework ApplicationContext
Spring beans are -scoped by default (@Scope("singleton")). The ApplicationContext acts as a managed Singleton registry — it creates each bean once and returns the same instance on every injection. Spring takes the management burden off the developer while preserving the one-instance guarantee.
Python logging module
logging.getLogger("myapp") always returns the same Logger instance for the same name. The logging module uses a module-level dictionary to cache loggers — effectively a Singleton registry — so that all code sharing a logger name sees the same configuration and handlers.
Your team is building a large backend application where multiple modules each call logging.getLogger('orders') at startup. A junior developer suggests that this will create multiple separate logger instances and could lead to inconsistent log configurations across modules. What actually happens, and why?
6. Interview Cheat Sheet
Key sentences to demonstrate depth:
- " combines two responsibilities — controlling instantiation and providing global access — which technically violates SRP, so I'd use it only when both constraints are genuinely required."
- "In Java, I prefer the
enumform because the JVM guarantees a single enum constant, making it serialization-safe and reflection-safe without any extra code." - "In Python, a metaclass override is cleaner than overriding
__new__because it doesn't interfere with the class's own__init__semantics." - "The biggest cost of is testability — global bleeds between tests unless you add a reset mechanism or inject the singleton as a dependency."
Common follow-up questions:
- "How do you make a Singleton thread-safe in Java?" → Use double-checked locking with
volatile, or the enum form which is JVM-guaranteed thread-safe. - "What's the difference between Singleton and a static class?" → A Singleton can implement interfaces, be subclassed, and passed as a dependency; a static class cannot. Singletons can be replaced with polymorphic alternatives; static classes cannot.
- "How do you test code that depends on a Singleton?" → Either extract an the Singleton implements and inject a mock, or add a package-private
resetInstance()method for tests.
Connections to other concepts:
- Monostate pattern — alternative where multiple instances share the same via static fields; avoids the enforcement mechanism but achieves the same effect.
- Registry pattern — a generalization: one instance per key, not one instance total. Often built using the same caching technique.
- — pairing Singleton with an allows the one instance to be replaced by a test double, restoring testability.
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.