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:
- Exactly one instance, enforced by the class itself.
- A global access point that’s safe and controlled.
The Solution
- Make the constructor private so no one can call
newfrom 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.
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.
Related Patterns
- 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