Singleton is a creational design pattern that restricts a class to exactly one instance and provides a single, well-known way to access it. The class typically hides its constructor and exposes a static method — often called getInstance() — that creates the instance on first call and returns the same one on every call after that. It is one of the oldest and most recognizable patterns in the Gang of Four catalog, and also one of the most argued about.
What changed in 2026
- Dependency injection containers absorbed most legitimate singleton use cases. Modern frameworks let you register a service as a singleton scope in a container, getting the one-instance guarantee without a hardcoded static accessor.
- Testing tooling made the downside more visible. As test suites grew and parallel test execution became standard, the shared mutable state that singletons introduce became a more obvious source of flaky, order-dependent tests.
- Language-level module systems reduced the need for it. In languages where a module is only evaluated once, a plain exported object often gives you singleton behavior without the pattern's ceremony.
How it works
class Config {
private static instance: Config;
private constructor() { /* load config */ }
static getInstance(): Config {
if (!Config.instance) {
Config.instance = new Config();
}
return Config.instance;
}
}
Every call to Config.getInstance() returns the same object. The constructor is private, so nothing outside the class can create a second instance directly. This is the mechanism — the debate is entirely about when using that mechanism is the right call.
When it is a legitimate choice
Singleton fits problems that are genuinely about coordinating a single shared resource: a connection pool that should not be duplicated, a logger that writes to one file, a cache that needs one source of truth in memory. In these cases, having two instances would be a bug, not just a style issue — the one-instance guarantee is solving a real problem, not just avoiding a function parameter.
Singleton vs the alternatives
| Approach |
One-instance guarantee |
Testability |
Global access |
| Classic singleton |
Yes, self-enforced |
Poor — hard to swap/mock |
Yes, global |
| Dependency injection (singleton scope) |
Yes, container-enforced |
Good — swap for a test double |
No, injected explicitly |
| Module-level export |
Yes, if module runs once |
Medium |
Yes, but scoped to imports |
| Plain instance passed explicitly |
No, up to the caller |
Best |
No |
Why it draws criticism
The complaint about singleton is rarely about the one-instance guarantee itself — it is about the global access point. A class that can be reached from anywhere becomes a hidden dependency: any function that calls Config.getInstance() has a dependency on global state that does not show up in its signature. That makes the function harder to test in isolation, since you cannot substitute a different config without reaching into global state to do it. This is the same underlying issue that shows up in discussions of the DRY vs WET tradeoff — a pattern applied for convenience rather than necessity tends to cost more than it saves once the codebase grows.
Common pitfalls
- Using singleton to dodge passing a parameter. If the only reason for the singleton is avoiding an extra constructor argument, dependency injection gives the same convenience without the hidden coupling.
- Making mutable singletons in multi-threaded or concurrent code. A shared mutable instance accessed from multiple threads without synchronization is a classic source of race conditions.
- Singleton-izing something that should scale per request. A per-user or per-request object forced into a singleton either leaks data across requests or requires awkward internal keying to fake per-request state.
FAQ
Is singleton considered an anti-pattern now?
Opinions vary. It is not inherently wrong, but the hardcoded, self-managed version is now considered inferior to a dependency-injected singleton scope for most application code, since it gives the same guarantee with better testability.
Does every logger need to be a singleton?
No. A single shared logger instance is often convenient, but passing a logger explicitly through a constructor is just as effective and easier to substitute during tests.
What is the difference between a singleton and a static class?
A static class has no instance at all — every member is accessed directly on the class. A singleton is a real object instance; it just happens to be limited to exactly one. That distinction matters if you ever need the object to implement an interface or be passed around.
Can singleton cause memory issues?
Rarely directly, but a singleton that accumulates state over the life of an application (a cache that never evicts, for example) can grow unbounded precisely because nothing ever creates a fresh instance to replace it.
Where to go next