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 objectJava
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.
Create once, return the cached object
Run the Python metaclass with the two DatabaseConnection calls from the doc: the first creates and caches the object, and the second returns it.
The first DatabaseConnection call holds the lock before checking the empty cache.
Text version
- The first DatabaseConnection call holds the lock before checking the empty cache.
- Because the class has no cached instance, the metaclass constructs the object.
- The metaclass caches the object while still holding the lock.
- The first call releases the lock and returns the cached object to db1.
- The second call holds the lock and skips construction because an instance is cached.
- It releases the lock and returns the same object to db2, so db1 is db2.
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 |
Compare Java Singleton choices
Compare basic lazy initialization, double-checked locking, and the Java enum form by mechanism, thread safety, and constraints.
Basic lazy creates on first access, double-checked locking checks for null twice, and the JVM creates the enum constant.
Text version
- Basic lazy creates on first access, double-checked locking checks for null twice, and the JVM creates the enum constant.
- Basic lazy creation can race, while double-checked locking with volatile and the enum form are thread-safe.
- Basic lazy fits only single-threaded apps, double-checked locking needs volatile so threads see the finished instance, and enum works only in Java.
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
Choose Singleton for a real sharing requirement
Use the required lifetime and testability needs to distinguish a shared Singleton from dependency injection or scoped storage.
Consider Singleton when one shared instance is a hard requirement, as for logging or a cache.
Text version
- Consider Singleton when one shared instance is a hard requirement, as for logging or a cache.
- If you only want easy access, inject the dependency instead.
- Use thread-local storage for each thread, or request-scoped dependency injection for each request.
- Choose by lifetime, and remember that a Singleton is hard to mock and keeps state between tests.
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.