On this page
Description
Singleton is the creational pattern for an object there must be exactly one of in the whole application - and a way for everyone to reach that one object. It's the simplest design pattern to write and the easiest to overuse, because "exactly one, reachable from anywhere" is also a description of a global variable. This skill covers when one instance is genuinely required, the ways to build it safely (lazily or eagerly, with threads), and what it costs.
Some objects stand for something the whole application must share: the loaded configuration, a pool of database connections, the client that holds a streaming connection to a feature-flag server. If two parts of the code each call new ConfigManager(), you get two copies that can drift apart - one reloaded, one stale - and nothing reports it. With a connection pool it is worse: two pools each believe they own the database's connection limit.
A public constructor can't prevent this - anyone can call it, as often as they like. Singleton takes the constructor away from callers and hands out one shared instance instead.
We'll go from simple to more technical:
- The pattern in Java and Python.
- Four more ways to write it, and when each one builds the instance.
- Thread safety: what the JVM and Python's import system guarantee, and what double-checked locking needs.
- Choosing among the creational patterns, and the mistakes to avoid.
The pattern
Java
class ConfigManager {
private static ConfigManager instance;
private ConfigManager() { /* load config */ }
public static synchronized ConfigManager getInstance() {
if (instance == null) instance = new ConfigManager();
return instance;
}
}
Python
import threading
class ConfigManager:
_instance: "ConfigManager | None" = None
_lock = threading.Lock()
@classmethod
def get_instance(cls) -> "ConfigManager":
with cls._lock:
if cls._instance is None:
cls._instance = cls() # load config
return cls._instance
The private constructor blocks new ConfigManager() from outside the class entirely - getInstance() is the only way in, and it always hands back the same object.
Python has no private constructors - nothing stops a call to ConfigManager() - so a Python singleton is usually just a module-level instance: config = ConfigManager() in config.py. A module runs once per process and is cached in sys.modules, so every from config import config gets the same object.
Four more ways to write it
The synchronized method above is correct, but it takes a lock on every call. These are the usual alternatives:
Java
// 1. Eager: built when the class is initialized. Thread-safe for free.
class Eager {
private static final Eager INSTANCE = new Eager();
private Eager() {}
static Eager getInstance() { return INSTANCE; }
}
// 2. Holder idiom: lazy AND lock-free. Holder is initialized only when getInstance() first touches it.
class Lazy {
private Lazy() {}
private static class Holder { static final Lazy INSTANCE = new Lazy(); }
static Lazy getInstance() { return Holder.INSTANCE; }
}
// 3. Double-checked locking: lazy, and locks only until the instance exists.
class Checked {
private static volatile Checked instance; // volatile is required
private Checked() {}
static Checked getInstance() {
Checked local = instance;
if (local == null) { // fast path: no lock once built
synchronized (Checked.class) {
local = instance;
if (local == null) instance = local = new Checked(); // second check, inside the lock
}
}
return local;
}
}
// 4. Enum: the JVM creates INSTANCE once and won't make more by reflection or deserialization.
enum Registry { INSTANCE; }
Python
# 1. Module-level instance: built when the module is first imported. The usual Python singleton.
# config.py
settings = Settings()
# elsewhere: from config import settings
# 2. Lazy, cached function: built on the first call, then cached.
import functools
@functools.cache
def get_settings() -> Settings:
return Settings()
# 3. Double-checked locking: lazy, and locks only until the instance exists.
import threading
class Checked:
_instance = None
_lock = threading.Lock()
@classmethod
def get_instance(cls):
if cls._instance is None: # fast path: no lock once built
with cls._lock:
if cls._instance is None: # second check, inside the lock
cls._instance = cls()
return cls._instance
# 4. __new__: every Registry() call returns the same object - but __init__ runs every time.
class Registry:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
When is "eager" actually built? Not when the class is loaded, but when it is initialized - on its first active use. If getInstance() is the only static member anyone touches, eager and lazy behave almost the same. The difference appears when the class has other static methods or fields: calling any of them builds the instance too. The holder idiom avoids that, because its nested class is initialized separately.
The eager form and the holder idiom get their thread safety from the JVM: a class's static initialization runs exactly once, and other threads wait until it finishes. Double-checked locking coordinates by itself, and both of its safety parts are required. Without the second check, two threads that both passed the first check each construct an instance. Without volatile, another thread can see the reference before the object's fields are written.
In Python, a module-level instance gets the same guarantee from the import system: a module's code runs once, under an import lock, and other threads importing it wait until it finishes. @functools.cache gives no such guarantee - two threads that call it at the same moment can both build an instance. Double-checked locking still needs both checks, but no volatile: the object is fully built before cls._instance = ... runs.
Choosing among the creational patterns
| If you need... | Reach for | Because |
|---|---|---|
| Exactly one shared instance, by design | Singleton (this lesson) | Two copies would be a bug (e.g. two connection pools fighting over the same database connections). |
| One place that decides which concrete class to build | Factory Method | Callers depend on an interface; adding a type changes the factory, not every caller. |
| A matched family of related objects that must vary together | Abstract Factory | Swapping the factory swaps the whole family, so mismatched pieces can't be combined. |
| An object with many optional parts, assembled step by step | Builder | Named, optional steps instead of long positional constructors; validate once in build(). |
| Many near-identical objects that are expensive to set up | Prototype | Copy a ready-made template instead of repeating costly construction. |
Common mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
| A Singleton because "there's only one right now" | a hidden global that's hard to replace in tests | create one instance at startup and pass it in |
Calling getInstance() inside business methods | the dependency is invisible and can't be faked | inject the shared instance through constructors |
| Lazy initialization with no lock | two threads can each build an instance | the holder idiom, an enum, a module-level instance, or a lock |
Double-checked locking without volatile (Java) | other threads can see a half-built object | declare the field volatile |
| Keeping mutable state in a singleton | tests leak state into each other; the state isn't shared across processes | keep it stateless, or move the state to a database or cache |
Check yourself
Try to answer before opening each one - recalling beats re-reading.
1. A class has private static final Cache INSTANCE = new Cache(); and a static Cache.version() method. A program only ever calls Cache.version(). Is the cache built?
Show answer
Yes. Calling any static method initializes the class, and initialization runs every static field initializer - including new Cache(). The holder idiom avoids this by keeping the instance in a nested class that only getInstance() touches.
2. Double-checked locking without volatile - what can go wrong?
Show answer
Another thread can see a non-null instance before the constructor's writes are visible to it, and use a half-built object. volatile forbids that reordering.
3. OrderService calls PaymentGateway.getInstance() inside its methods. Why is OrderService hard to unit test?
Show answer
The dependency is hidden and global, so a test can't pass in a fake gateway. Passing the gateway through OrderService's constructor fixes that - and the application can still create exactly one gateway and hand it to everyone.
Key takeaways
- Use it only when a second instance would be wrong, not merely unnecessary.
- Prefer an
enum(or an eagerly createdstatic finalfield) unless you truly need lazy creation - in Python, a module-level instance. - Lazy + multi-threaded means the holder idiom, a
synchronizedmethod, or double-checked locking with avolatilefield - in Python, athreading.Lockaround the check, or double-checked. - It behaves like a global variable - that is the price you pay in testability.
- "Exactly one" only holds inside a single JVM (or Python process - each worker of a multi-process server has its own); once you scale out, the shared state must move to a database or cache.