# Singleton

> Guarantee one instance of a class per process with a global access point. Usually better replaced by one instance created at startup and injected.

- kind: pattern
- category: creational
- canonical: https://aipatterns.dev/design-patterns/singleton
- inspired-by: https://refactoring.guru/design-patterns/singleton

## Intent

Ensure a class has exactly one instance in the process and give code a well-known way to reach it. The class itself controls its creation, so no caller can make a second copy by accident.

## Forces

- **One shared resource** vs **many callers**: a metrics registry, connection pool or cache must be shared, or its data splits across copies.
- **Global access** vs **visible dependencies**: reaching for `Registry.instance()` anywhere is convenient, but hides what a function depends on.
- **Lazy creation** vs **thread safety**: creating on first use is cheap until two threads race to do it.
- **One instance in production** vs **fresh instances in tests**: state that leaks between tests makes them order-dependent.

## Therefore

**Create the one instance in one place, and hand it to the code that needs it. Use the class-enforced global accessor only at the edge, if at all.**

The classic form hides the constructor and exposes a static accessor that creates the instance lazily and safely. That guarantees uniqueness, but it also makes the instance a global variable. The modern form keeps the "one instance" part and drops the "global access" part: the composition root creates the object once (or asks the accessor once) and passes it through constructors. Most languages also have a cheaper way to get one instance: a module-level value (TypeScript, Python), `sync.OnceValue` (Go), or a holder class or `enum` (Java).

## Structure

```mermaid
classDiagram
  class MetricsSink {
    <<interface>>
    +increment(name) void
  }
  class MetricsRegistry {
    -MetricsRegistry instance
    -Map counters
    +instance() MetricsRegistry
    +increment(name) void
    +snapshot() Map
  }
  class CheckoutService {
    -MetricsSink metrics
    +placeOrder(id) void
  }
  class CompositionRoot {
    +main() void
  }
  MetricsSink <|.. MetricsRegistry
  CheckoutService --> MetricsSink
  CompositionRoot ..> MetricsRegistry : gets the instance once
  CompositionRoot ..> CheckoutService : injects it
```

### Participants

- **Singleton** (`MetricsRegistry`): holds the single instance and controls its creation, lazily and thread-safely.
- **Abstraction** (`MetricsSink`): the narrow interface consumers depend on, so tests can pass a fake.
- **Consumers** (`CheckoutService`): receive the instance through their constructor and never call the static accessor.
- **Composition root** (`main`): the only place that touches the global accessor or module-level instance.

## Use it when

- Exactly one instance must exist for correctness, not convenience: one process-wide metrics registry, one connection pool per database, one in-process cache whose entries must be shared.
- The instance is expensive to create and must be created lazily, on first use.
- A framework or plugin system offers no injection point, and the shared object has to be reachable from callbacks you don't construct.
- You are wrapping a process-wide resource that is already unique: stdout, the environment, a native library handle.

## Do not use it when

- You want "only one" merely because the app creates only one. Create it in `main` and inject it. Uniqueness enforced by convention at the composition root is enough, and it keeps the dependency visible.
- One instance per process is enough and load-time creation is acceptable. A module-level constant (`export const metrics = new MetricsRegistry()` in TypeScript or Python) is already a per-process single instance, with no private constructor or static accessor. Singleton's controlled construction matters only when creation is expensive, must wait for runtime configuration, or a second instance would be a bug.
- You use a DI container. Register the class with singleton scope (Spring beans, .NET `AddSingleton`, NestJS providers) and let the container own its lifetime.
- The object holds per-request or per-user state. A singleton turns that into cross-request leakage, and in multi-tenant code into a data exposure bug.
- Tests need isolation. Every `Foo.instance()` call inside business logic is a hidden global that tests can't replace without reset hooks. Pass the instance as a parameter instead.
- Configuration is the "shared" thing. Parse config once and pass the values; a global config object makes every function implicitly depend on every setting.

## Application grill

**Inspect first:** the shared instances today (module scope in Node is already one per process; DI containers; global mutable state); how tests reset or fake the instance now; whether "one" means one per process or one across the fleet.

**Missing pieces:** none: module scope plus constructor injection replaces it. If "one" really means fleet-wide (one scheduler, one leader), that is a different problem - a database advisory lock or leader election - and this pattern does not provide it.

**Ask before you build:**

- **Scale:** does "one" mean per process, per instance, or across the fleet?
- **Performance:** does lazy initialization stall the first request that pays for setup?
- **Reliability:** when the single instance holds state, what happens on redeploy or restart - who rebuilds it?
- **Budget:** what does the global state cost in test isolation (order-dependent suites, flaky CI)?
- **Implementation:** how is the instance provided - import side effect or injection - and how do tests substitute it?

## Implementation steps

1. Confirm a second instance would be a bug, not just a waste. If not, stop and inject a normal instance.
2. Extract a narrow interface for what consumers actually call (`increment`), and change consumers to take it through their constructor.
3. Create the instance in one place, preferring the language's idiom: a module-level value, `sync.OnceValue`, a holder class or an `enum`.
4. If you need class-enforced uniqueness, hide the constructor and add a static accessor that creates the instance lazily and thread-safely.
5. Call the accessor only at the composition root. Search for remaining `instance()` calls in business code and replace them with injected parameters.
6. In tests, construct consumers with a fake or a fresh instance. Don't add `resetForTests()` hooks to the singleton.

## Example

A web service counts business events (orders placed, payments failed) in one in-process metrics registry that a `/metrics` endpoint reads. Two registries would silently split the counts. Each version below uses its language's idiom for one instance, and `CheckoutService` receives it through its constructor.

```ts title="metrics.ts"
interface MetricsSink {
  increment(name: string): void;
}

class MetricsRegistry implements MetricsSink {
  private static current: MetricsRegistry | undefined;
  private readonly counters = new Map<string, number>();

  private constructor() {}

  // Classic accessor. A module-level `export const metrics = ...` gives the same
  // guarantee in a module system; JavaScript is single-threaded, so no lock is needed.
  static instance(): MetricsRegistry {
    return (MetricsRegistry.current ??= new MetricsRegistry());
  }

  increment(name: string): void {
    this.counters.set(name, (this.counters.get(name) ?? 0) + 1);
  }

  snapshot(): Record<string, number> {
    return Object.fromEntries(this.counters);
  }
}

class CheckoutService {
  // Depends on the interface, so tests pass a fake instead of the global.
  constructor(private readonly metrics: MetricsSink) {}

  placeOrder(orderId: string): void {
    if (!orderId) {
      this.metrics.increment("orders.rejected");
      return;
    }
    this.metrics.increment("orders.placed");
  }
}

// Composition root: the only place that touches the accessor.
const registry = MetricsRegistry.instance();
const checkout = new CheckoutService(registry);
checkout.placeOrder("A-1001");
checkout.placeOrder("A-1002");
checkout.placeOrder("");

console.log(registry === MetricsRegistry.instance()); // true
console.log(registry.snapshot()); // { 'orders.placed': 2, 'orders.rejected': 1 }

// In a test: no global involved.
const seen: string[] = [];
new CheckoutService({ increment: (n) => seen.push(n) }).placeOrder("T-1");
console.log(seen); // [ 'orders.placed' ]
```

```python title="metrics.py"
import threading
from collections import Counter
from typing import Protocol

class MetricsSink(Protocol):
    def increment(self, name: str) -> None: ...

class MetricsRegistry:
    def __init__(self) -> None:
        self._lock = threading.Lock()
        self._counters: Counter[str] = Counter()

    def increment(self, name: str) -> None:
        with self._lock:
            self._counters[name] += 1

    def snapshot(self) -> dict[str, int]:
        with self._lock:
            return dict(self._counters)

# A module is imported once per process, so this is the single instance.
# No __new__ tricks or metaclasses needed.
metrics = MetricsRegistry()

class CheckoutService:
    def __init__(self, sink: MetricsSink) -> None:
        self._metrics = sink

    def place_order(self, order_id: str) -> None:
        if not order_id:
            self._metrics.increment("orders.rejected")
            return
        self._metrics.increment("orders.placed")

class RecordingSink:
    def __init__(self) -> None:
        self.seen: list[str] = []

    def increment(self, name: str) -> None:
        self.seen.append(name)

if __name__ == "__main__":
    checkout = CheckoutService(metrics)  # composition root injects the module instance
    for order_id in ("A-1001", "A-1002", ""):
        checkout.place_order(order_id)
    print(metrics.snapshot())  # {'orders.placed': 2, 'orders.rejected': 1}

    fake = RecordingSink()  # tests never touch the global
    CheckoutService(fake).place_order("T-1")
    print(fake.seen)  # ['orders.placed']
```

```go title="metrics.go"
package main

import (
	"fmt"
	"sync"
)

type MetricsSink interface {
	Increment(name string)
}

type Registry struct {
	mu       sync.Mutex
	counters map[string]int
}

func (r *Registry) Increment(name string) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.counters[name]++
}

func (r *Registry) Snapshot() map[string]int {
	r.mu.Lock()
	defer r.mu.Unlock()
	out := make(map[string]int, len(r.counters))
	for k, v := range r.counters {
		out[k] = v
	}
	return out
}

// DefaultRegistry is created once, on first call, safely across goroutines.
var DefaultRegistry = sync.OnceValue(func() *Registry {
	return &Registry{counters: map[string]int{}}
})

type CheckoutService struct{ metrics MetricsSink }

func (s CheckoutService) PlaceOrder(orderID string) {
	if orderID == "" {
		s.metrics.Increment("orders.rejected")
		return
	}
	s.metrics.Increment("orders.placed")
}

type recordingSink struct{ seen []string }

func (r *recordingSink) Increment(name string) { r.seen = append(r.seen, name) }

func main() {
	reg := DefaultRegistry() // composition root: the only call to the accessor
	checkout := CheckoutService{metrics: reg}
	for _, id := range []string{"A-1001", "A-1002", ""} {
		checkout.PlaceOrder(id)
	}
	fmt.Println(reg == DefaultRegistry()) // true
	fmt.Println(reg.Snapshot())           // map[orders.placed:2 orders.rejected:1]

	fake := &recordingSink{}
	CheckoutService{metrics: fake}.PlaceOrder("T-1")
	fmt.Println(fake.seen) // [orders.placed]
}
```

```java title="Metrics.java"
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

public class Metrics {
    interface MetricsSink { void increment(String name); }

    static final class MetricsRegistry implements MetricsSink {
        private final ConcurrentHashMap<String, Long> counters = new ConcurrentHashMap<>();

        private MetricsRegistry() {}

        // Holder idiom: the JVM loads Holder lazily and initializes it exactly once,
        // so this is thread-safe without locks or double-checked locking.
        private static final class Holder {
            static final MetricsRegistry INSTANCE = new MetricsRegistry();
        }

        static MetricsRegistry instance() { return Holder.INSTANCE; }

        public void increment(String name) { counters.merge(name, 1L, Long::sum); }

        Map<String, Long> snapshot() { return new TreeMap<>(counters); }
    }

    static final class CheckoutService {
        private final MetricsSink metrics;

        CheckoutService(MetricsSink metrics) { this.metrics = metrics; }

        void placeOrder(String orderId) {
            metrics.increment(orderId.isEmpty() ? "orders.rejected" : "orders.placed");
        }
    }

    public static void main(String[] args) {
        // Composition root: the only call to instance(). With Spring, a @Component
        // bean (singleton scope by default) replaces all of the above.
        MetricsRegistry registry = MetricsRegistry.instance();
        var checkout = new CheckoutService(registry);
        for (String id : List.of("A-1001", "A-1002", "")) checkout.placeOrder(id);

        System.out.println(registry == MetricsRegistry.instance()); // true
        System.out.println(registry.snapshot()); // {orders.placed=2, orders.rejected=1}

        List<String> seen = new ArrayList<>();
        new CheckoutService(seen::add).placeOrder("T-1"); // tests never touch the global
        System.out.println(seen); // [orders.placed]
    }
}
```

## Trade-offs

**Gains**
- A second instance can't be created by mistake.
- Lazy, thread-safe creation is solved once, in one place.
- Every part of the process sees the same shared state.

**Costs**
- The accessor is a global variable: any function can depend on it without saying so in its signature.
- Tests share state across cases unless every consumer takes the instance as a parameter.
- It mixes two jobs, the object's real work and lifetime management, in one class.
- "One per process" breaks down across worker processes, serverless instances or clustered nodes, where each gets its own copy.
- Lazy initialization in languages with threads is easy to get wrong when written by hand.

## Relations

- [Facade](https://aipatterns.dev/design-patterns/facade.md) objects are often needed only once, but that is a reason to create one, not to make the class a singleton.
- [Flyweight](https://aipatterns.dev/design-patterns/flyweight.md) also shares instances, but many of them, one per distinct intrinsic state. A singleton is exactly one, with mutable state allowed.
- [Abstract Factory](https://aipatterns.dev/design-patterns/abstract-factory.md) implementations, builders and prototype registries are often made singletons. Usually a module-level value or a single injected instance is enough.
- [Builder](https://aipatterns.dev/design-patterns/builder.md) and [Prototype](https://aipatterns.dev/design-patterns/prototype.md) registries are similar candidates: one shared catalog, created at startup and passed around.

## Agent checklist

- [ ] A second instance would cause incorrect behavior, not only extra memory. Otherwise create one instance at startup and inject it.
- [ ] You used the language idiom (module-level value, `sync.OnceValue`, holder class, `enum`, or DI singleton scope) rather than hand-rolled locking.
- [ ] Business code receives the instance through a constructor or parameter; the static accessor is called only at the composition root.
- [ ] The singleton holds no per-request, per-user or per-tenant state.
- [ ] Tests construct consumers with a fake or fresh instance and need no reset hook on the singleton.
