| Lesson 4 | Singleton: applicability |
| Objective | Learn when you should and should not use Singletons. |
Singleton is a creational pattern used to enforce exactly one instance of a class within a given runtime boundary (typically a single process) and to provide a controlled way to access that instance. This lesson focuses on the practical question: when is Singleton a good design decision, and when does it create more problems than it solves?
A modern clarification: Singleton almost always means one instance per process (or per application container). In microservices, server farms, and distributed systems, each process can have its own Singleton. If you need “only one active coordinator across machines,” you usually need an external mechanism (distributed lock, leader election, database constraint, or coordination service), not an in-process Singleton.
Singleton is a reasonable fit when all (or most) of the following are true:
Singleton is a poor choice when any of the following are true:
In modern application architecture, Singletons are often replaced by a container-managed singleton scope (dependency injection frameworks, service locators used sparingly, or application composition roots). These approaches preserve “one instance per runtime” while making dependencies explicit and improving testability.
The classic GoF framing for Singleton applicability includes ideas like:
In modern codebases, the first criterion still holds. The second criterion is less commonly a deciding factor because many teams now prefer composition over inheritance, and they model variability via interfaces and injected implementations rather than subclassing a Singleton directly. If you want a “single instance of something pluggable,” it is usually cleaner to keep: an interface + a single concrete instance created by your composition root or DI container.
If your Singleton grows into a hub for unrelated features (configuration, logging, caching, feature flags, IO, security context, etc.), it will become hard to test and hard to change. This is one of the most common reasons modern teams avoid explicit Singletons.
Older Singleton examples often “detect multiple instances” using a static flag inside the constructor. That approach is fragile because it does not reliably prevent construction, it does not address concurrency, and it can create confusing error paths. A correct Singleton uses a private constructor and a controlled accessor.
// Legacy illustration (NOT recommended): a static flag checked in a constructor
static boolean instance_flag = false;
If you need to fail fast when construction is attempted twice, throwing an exception can be useful in educational examples. In production systems, the stronger approach is to prevent construction altogether (private constructor), and let the controlled accessor be the only entry point.
class SingletonException extends RuntimeException {
public SingletonException() { super(); }
public SingletonException(String s) { super(s); }
}
Modern caution: “throw on second construction” is not how most real systems enforce Singleton. The constructor should generally be private, so outside code cannot call it. If reflection/serialization can create instances in your language/runtime, you must consider those paths explicitly (or rely on language idioms such as enums or container-managed singletons).