Singleton Pattern: One Instance, One Hidden Cost

Singleton guarantees one globally reachable instance, but the convenience behaves like shared global state and must be treated as a trade-off.

What is the Singleton pattern?

The Singleton pattern is a creational design pattern that ensures a class has exactly one instance and provides a single global point of access to it. The class hides its constructor and exposes a static accessor that creates the instance on first use and returns the same object ever after.

It is also the most argued-about pattern in the catalog, because "one instance, reachable from anywhere" is another way of saying "global mutable state." Singletons hide dependencies (nothing in a function signature reveals it touches one), leak state between tests, and stop being unique the moment your code runs in multiple processes, workers, or containers. Treat uniqueness as an invariant to enforce deliberately — not a convenience to reach for.

The logic, step by step

Problem

A process appears to need exactly one coordinator, yet unrestricted construction could create conflicting instances.

Rule

Control construction and expose one access point only when uniqueness is a real invariant of the running process.

Run
  1. FIRST ACCESS checks whether the shared instance already exists.
  2. CONSTRUCTOR creates the instance only when no instance is available.
  3. LATER CALLS receive the same instance and therefore the same state.
Output

The process observes one coordinated instance instead of accidentally creating competing copies.

Singleton pattern example in TypeScript

TypeScript
class ConnectionPool {
  private static instance: ConnectionPool | null = null;
  private readonly connections: Connection[] = [];

  private constructor(size: number) {
    for (let i = 0; i < size; i++) this.connections.push(openConnection());
  }

  static get(): ConnectionPool {
    // Lazy: created on first access, shared ever after.
    ConnectionPool.instance ??= new ConnectionPool(10);
    return ConnectionPool.instance;
  }

  lease(): Connection { /* hand out an idle connection */ }
}

const pool = ConnectionPool.get();

// The often-better alternative: create once at the entry point,
// then pass it down explicitly. Uniqueness by discipline, not by lock.
// const pool = new ConnectionPool(10);
// startServer({ pool });
A pool is a legitimate singleton candidate: duplicating it defeats its purpose. The commented alternative shows scoped injection, which keeps the same uniqueness with visible dependencies.

Run it in the real world

A desktop application may coordinate one window registry, while a web service should usually inject scoped dependencies instead of assuming one global process.

When to use it — and when not to

Reach for Singleton when

  • Duplicating the resource is actively harmful: connection pools, hardware handles, an app-wide cache within one process.
  • The instance is expensive to build and genuinely process-wide.
  • You control a single-process runtime (desktop app, CLI) where "one per process" means "one."

Skip it when

  • You want convenient global access to something injectable — loggers, config, clocks. Inject them instead.
  • Code runs in multiple workers or distributed instances; the "single" instance silently becomes N instances.
  • Tests need isolation — shared singleton state is the classic cause of order-dependent test failures.

Facade pattern — A facade is often held as a single shared instance, which tempts people into Singleton. Prefer creating the facade once at composition root and injecting it — you keep one instance without the global access.

Frequently asked questions

Why is the Singleton pattern considered an anti-pattern?

Because it combines two decisions that deserve separation: limiting instantiation to one instance, and making that instance globally reachable. The first is occasionally necessary; the second hides dependencies, couples code to concrete classes, and makes tests interfere with each other.

How do you make a Singleton thread-safe?

In Java/C# use an eagerly initialized static field, a holder class, or double-checked locking with volatile. In JavaScript single-threaded code plus module caching gives you thread safety for free — an exported module constant is already a singleton.

What should I use instead of a Singleton?

Create the object once at your application entry point and pass it to whatever needs it (manually or via a DI container). You keep the single instance while dependencies stay explicit and replaceable in tests.