Singleton

Ensure a class has exactly one instance and provide a global access point to it. Useful for shared resources like config managers, connection pools, and loggers — but treat it with caution.

The Problem

Some resources should exist only once: a configuration store, a database connection pool, a logging system. Normal constructors always create a new object — there’s no built-in way to enforce uniqueness. You could use a global variable, but globals are unsafe: any code can overwrite them.

You need two things simultaneously:

  1. Exactly one instance, enforced by the class itself.
  2. A global access point that’s safe and controlled.

The Solution

  • Make the constructor private so no one can call new from outside.
  • Add a static method that returns the single instance, creating it on the first call and returning the cached copy on every subsequent call.
Singleton - instance: Singleton - data: any + getInstance(): Singleton + businessLogic() private constructor getInstance() ClientA ClientB ClientC
Singleton — private constructor, static getInstance(), all clients share one instance

Real-World Analogy

A country has only one official government at a time. “The Government of France” always refers to the same body — regardless of who’s asking. The title itself is the access point; you can’t accidentally create a second government by calling new Government().


TypeScript Example

class Logger {
  private static instance: Logger | null = null;
  private logs: string[] = [];

  // Block external instantiation
  private constructor(private readonly prefix: string) {}

  // The single access point
  static getInstance(): Logger {
    if (!Logger.instance) {
      Logger.instance = new Logger('[App]');
    }
    return Logger.instance;
  }

  log(message: string): void {
    const entry = `${this.prefix} ${new Date().toISOString()} — ${message}`;
    this.logs.push(entry);
    console.log(entry);
  }

  getLogs(): string[] {
    return [...this.logs];
  }
}

// Usage
const loggerA = Logger.getInstance();
const loggerB = Logger.getInstance();

console.log(loggerA === loggerB); // → true — same instance

loggerA.log('Server started');
loggerB.log('Request received'); // both go to the same log array

console.log(loggerA.getLogs().length); // → 2

Thread-Safe Singleton (Node.js / Worker context)

In multi-threaded environments (worker threads, Deno), lazy initialisation can race. One solution: eager initialisation — create the instance at module load time.

class Config {
  // Eagerly created — no race condition possible
  private static readonly instance = new Config();

  private constructor() {
    // Load config once
  }

  static getInstance(): Config {
    return Config.instance;
  }

  get(key: string): string | undefined {
    // ...
    return undefined;
  }
}

Module-Level Singleton (Modern TypeScript)

In ES modules, simply export a single instance. The module loader caches it:

// config.ts
class AppConfig {
  readonly apiUrl = process.env.API_URL ?? 'http://localhost:3000';
  readonly debug  = process.env.DEBUG === 'true';
}

// Single export — module caching makes it a Singleton automatically
export const config = new AppConfig();

// Anywhere in your app:
import { config } from './config';
config.apiUrl; // always the same object

When to Use

  • A single shared instance is genuinely required (database pool, config, logger, event bus)
  • You want controlled, protected global access — safer than a plain global variable
  • You need lazy initialisation (create only when first needed)

Pros

  • Guarantees one instance — prevents accidental duplication of expensive resources
  • Lazy initialisation — created only when first requested
  • Provides a well-known access point for shared state

Cons

  • Violates the Single Responsibility Principle — the class manages both its business logic and its own lifecycle
  • Makes unit testing harder — static state persists between tests; mocking requires extra work
  • Can mask bad architecture — if many classes need a Singleton, it may indicate global state that should be passed explicitly (dependency injection)
  • Requires special care for thread safety in multi-threaded environments

Tip: Prefer dependency injection over Singleton for testable code. Inject the shared instance where it’s needed rather than calling getInstance() deep inside business logic.


  • Facade is often implemented as a Singleton — one entry point to a subsystem
  • Abstract Factory, Builder, and Prototype can all be implemented as Singletons
  • Monostate is an alternative where multiple instances share the same state via static fields