Design Patterns «Prev Next»

Lesson 4Singleton: applicability
ObjectiveLearn when you should and should not use Singletons.

Singleton Applicability: When to Use It, When to Avoid It

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?

Quick rule of thumb

  • Use Singleton when you truly need one authoritative coordinator for a narrowly-scoped responsibility and it would be incorrect or wasteful to have multiple instances.
  • Avoid Singleton when you are using it mainly for convenience (a global variable with a nicer name), or when it hides dependencies and makes testing, concurrency, or lifecycle management harder.

Singleton is “one per boundary,” not “one globally”

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.

When you should use Singletons

Singleton is a reasonable fit when all (or most) of the following are true:

  1. You need a single shared instance for correctness. Multiple instances would cause conflicting decisions or inconsistent state.
    • Examples: a window manager, a print spooler coordinator, a process-wide metrics registry.
  2. You are coordinating access to a scarce or exclusive resource. The resource itself cannot be safely or meaningfully “duplicated.”
    • Examples: an audio device controller, a device driver façade, a unique hardware interface.
  3. You have a clear lifecycle story. You know when the instance should be created (eager vs. lazy), and how it should be disposed/shut down if needed.
  4. The responsibility is narrow and stable. The Singleton acts as a focused coordinator, not a “god object” that accumulates unrelated functionality.
  5. You can keep state minimal or immutable. If the Singleton must hold mutable state, it should be well-encapsulated, thread-safe, and intentionally designed.

When you should NOT use Singletons

Singleton is a poor choice when any of the following are true:

  1. You mainly want convenient access. If the motivation is “I don’t want to pass dependencies around,” you are usually creating hidden coupling.
  2. You need multiple configurations or multiple environments. Test harnesses, multi-tenant systems, and plugins often require multiple instances with different settings.
  3. State leaks across tests. A Singleton that retains state can make unit tests order-dependent and flaky unless you add reset hooks (which is itself a smell).
  4. Concurrency is non-trivial. A Singleton that is accessed widely can become a contention hotspot or a source of subtle race conditions.
  5. You are building distributed behavior. A Singleton does not enforce “only one” across nodes. Treat it as a local implementation detail, not a distributed guarantee.

Prefer modern alternatives when the goal is lifecycle management

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.

  • Dependency Injection: the container creates one instance and injects it where needed.
  • Factory + explicit ownership: a top-level object (composition root) constructs and passes dependencies.
  • Stateless utility: if there is no state, you may not need an instance at all—prefer pure functions.

Applicability criteria: classic vs. modern interpretation

The classic GoF framing for Singleton applicability includes ideas like:

  1. Exactly one instance, accessible through a class-provided access method.
  2. Reuse/extension considerations, such as allowing subclassing without changing clients.

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.

Singleton anti-pattern warning: the “global variable with methods”

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.

Implementation notes (and why the legacy approach is risky)

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).

Checklist: decide quickly

  1. Correctness: Would multiple instances break the system or violate a hard constraint?
  2. Scope: Do you mean one per process, one per container, or one across the fleet?
  3. State: Can the instance be mostly immutable and free of hidden global state?
  4. Testing: Can consumers be tested without relying on persistent global state?
  5. Alternatives: Would DI/composition express the same intent more cleanly?
clients: Any object or class outside the pattern; generally one that only knows about the public interface the pattern and its classes present, rather than about its private implementation.

SEMrush Software 4 SEMrush Banner 4