designpattern.site

Singleton Pattern

Ensure a class has only one instance and provide a global access point to it.

Overview

Some resources in a system should exist exactly once. A database connection pool, an application-wide logger, or a configuration manager loaded from disk — creating multiple copies of these wastes memory, causes inconsistent state, and can introduce hard-to-trace bugs.

The Singleton pattern is a creational design pattern that solves this by restricting instantiation of a class to a single object. It also gives every part of the codebase a consistent way to reach that object, without passing references around manually.

Despite its simplicity, Singleton is one of the most debated patterns in software design. Used in the right context it solves a real problem cleanly. Used carelessly it becomes hidden global state that makes code hard to test and reason about.

Problem & Motivation

Imagine you are building an application that reads configuration from a YAML file on startup. The config object is expensive to create — it does file I/O, validates keys, and builds a lookup structure. If every module that needs config simply calls new Config(), you end up with:

  • Multiple disk reads for the same file
  • Divergent config state if one instance loads before a file write and another loads after
  • No single place to override config in tests

The naive fix — a global variable — works but exposes the raw object everywhere and gives no control over initialization order. Singleton formalizes this into a pattern: the class itself manages its own single instance and controls when it is created.

Class Diagram

Loading diagram...

The key structural points are:

  • The constructor is private, so external code cannot call new Singleton().
  • instance is a private static field that holds the one object.
  • getInstance() is a public static method — the only door into the class.

Implementation

The implementations below model a ConfigManager that loads settings once and returns them on every subsequent call. All four versions produce the same observable behavior: two calls to getInstance() return the same object.

class ConfigManager {
  private static instance: ConfigManager;
  private settings: Record<string, string>;

  private constructor() {
    // Simulate loading from disk
    this.settings = {
      dbHost: "localhost",
      dbPort: "5432",
      logLevel: "info",
    };
    console.log("ConfigManager initialized");
  }

  static getInstance(): ConfigManager {
    if (!ConfigManager.instance) {
      ConfigManager.instance = new ConfigManager();
    }
    return ConfigManager.instance;
  }

  get(key: string): string | undefined {
    return this.settings[key];
  }
}

// Usage
const config1 = ConfigManager.getInstance();
const config2 = ConfigManager.getInstance();

console.log(config1 === config2); // true — same object
console.log(config1.get("dbHost")); // "localhost"

Thread Safety

In single-threaded environments (most browser JS, simple scripts) the basic null-check is fine. In multi-threaded environments — Java servers, Python with real threads, concurrent Go goroutines — you must use locking or atomic primitives. Java's volatile + double-checked locking and Go's sync.Once are the idiomatic choices. Forgetting this is the most common Singleton bug.

Real-World Examples

Database connection pools — A pool manages a fixed set of reusable connections. Creating a second pool duplicates connections and can exceed the database's connection limit. Almost every ORM (Hibernate, SQLAlchemy, GORM) keeps its session factory or engine as a singleton internally.

Application loggerslogging.getLogger(__name__) in Python returns the same logger instance for a given name every time it is called. The logging framework manages a global registry of singletons, so all modules write to the same handlers and respect the same log level.

Feature flag / configuration services — Services like LaunchDarkly or a homegrown FeatureFlags class load their flag definitions once from an API or config file. A singleton ensures every call site reads from the same in-memory snapshot and that the polling/refresh loop runs exactly once.

OS-level resources — Clipboard managers, hardware device handles, and OS-level sockets are often modeled as singletons because the underlying resource is itself singular. Creating two clipboard objects in the same process would result in conflicting writes to a resource that only has one owner.

Pros and Cons

Advantages

Controlled instantiation — the class enforces its own constraint; callers cannot accidentally create a second instance.

Lazy initialization — the instance is created only when first requested, not at program startup, which keeps startup time fast.

Single source of truth — all code that reads from the object sees the same data, eliminating an entire class of consistency bugs.

Reduced overhead — expensive resources (connections, loaded files, parsed configs) are paid for once and reused.

Disadvantages

Hidden global state — Singleton is essentially a global variable in disguise. Any code anywhere can mutate the shared instance, making cause-and-effect hard to follow.

Testing is painful — unit tests that rely on a Singleton carry state from test to test unless you add a backdoor to reset the instance. This violates test isolation.

Violates Single Responsibility Principle — the class is responsible both for its own business logic and for managing its own lifecycle and access.

Concurrency complexity — in multi-threaded contexts, the initialization guard and any mutable state require careful locking. Missing a lock is a silent bug that only surfaces under load.

Tight coupling — callers that call Singleton.getInstance() directly are coupled to the concrete class. Swapping implementations (e.g., for a test double) requires modifying production code.

When to Use / When to Avoid

Use Singleton when:

  • The resource being managed is genuinely singular at the OS or infrastructure level (one database, one config file, one log stream).
  • Initialization is expensive and should happen exactly once.
  • You need a well-defined coordination point — for example, a shared cache or an event bus.
  • The codebase is small enough that global state is easily understood.

Avoid Singleton when:

  • You find yourself adding Singleton to make access convenient rather than because the resource must be singular.
  • The class holds mutable state — shared mutable state across threads is a concurrency hazard.
  • You need to run tests in parallel or swap implementations per test.
  • Multiple environments or tenants in the same process each need their own instance.
  • Dependency injection is already available — inject the shared instance rather than reaching for it globally.

Registry — a generalization of Singleton. Instead of a single hard-coded instance, a Registry maps names to instances, supporting multiple named singletons. Useful when you need a controlled number of instances rather than exactly one.

Multiton — extends Singleton by managing a map of instances keyed by a parameter (e.g., locale or database name). Each key maps to its own singleton. This solves the "one per X" problem cleanly.

Dependency Injection — the modern alternative to Singleton in application code. Rather than having the class find itself, a DI container creates a shared instance once and injects it wherever it is declared as a dependency. This gives you the same sharing behavior without coupling callers to the concrete class, and makes testing straightforward.

Design Tip

If you are reaching for Singleton to solve a "who creates this?" problem, consider Dependency Injection first. DI frameworks (Spring, Angular, NestJS, Wire for Go) manage object lifetimes — including singleton scope — without the coupling and testing burdens of the classic pattern. Reserve hand-rolled Singleton for cases where you have no DI container and the resource is genuinely singular.

On this page