Singleton Pattern: A Controlled Global Instance

What is the Singleton Pattern?
The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. It's especially useful when you need to control access to shared resources like database connections, connection pools, or application configurations.
classDiagram
class Singleton {
-instance: Singleton$
-Singleton()
+getInstance(): Singleton$
+operation()
}
note for Singleton "instance is static\ngetInstance() is static\nConstructor is private"When to Use Singleton
Use Singleton:
- Database connections
- Application configuration
- Connection pools
- Centralized loggers
Avoid Singleton:
- In multi-threaded contexts without proper synchronization
- When it makes unit testing difficult
- If it introduces unnecessary global coupling
Step by Step Implementation
1. Private Constructor
The private constructor prevents direct external instantiation with new:
class DatabaseConnection {
private connectionString: string;
private constructor(connectionString: string) {
this.connectionString = connectionString;
}
}2. Static Property for the Instance
The class maintains a static reference to its only instance:
private static instance: DatabaseConnection | null = null;3. Access Method (getInstance)
Global access point that creates the instance only if it doesn't exist (Lazy Initialization):
public static getInstance(connectionString?: string): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection(
connectionString ?? 'mongodb://localhost:27017/default'
);
}
return DatabaseConnection.instance;
}4. Reset Method for Testing
Allows resetting the instance in unit tests:
public static resetInstance(): void {
DatabaseConnection.instance = null;
}Complete Practical Example
interface User {
id: string;
name: string;
}
class DatabaseConnection {
private static instance: DatabaseConnection | null = null;
private connectionString: string;
private connected: boolean = false;
private constructor(connectionString: string) {
this.connectionString = connectionString;
}
public static getInstance(connectionString?: string): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection(
connectionString ?? 'mongodb://localhost:27017/default'
);
}
return DatabaseConnection.instance;
}
public static resetInstance(): void {
DatabaseConnection.instance = null;
}
async connect(): Promise<void> {
if (this.connected) return;
console.log(`Connecting to ${this.connectionString}...`);
this.connected = true;
}
async query<T>(sql: string): Promise<T[]> {
console.log(`Executing: ${sql}`);
return [] as T[];
}
}
// Usage
const db1 = DatabaseConnection.getInstance('mongodb://prod:27017/app');
const db2 = DatabaseConnection.getInstance();
console.log(db1 === db2); // true - same instance
await db1.connect();
const users = await db2.query<User>('SELECT * FROM users');Variant: Singleton with Symbol
To guarantee uniqueness even across different modules:
const SINGLETON_KEY = Symbol.for('app.singleton.config');
class ConfigurationManager {
private config: Map<string, unknown> = new Map();
static get instance(): ConfigurationManager {
const globalAny = globalThis as Record<symbol, ConfigurationManager>;
if (!globalAny[SINGLETON_KEY]) {
globalAny[SINGLETON_KEY] = new ConfigurationManager();
}
return globalAny[SINGLETON_KEY];
}
set<T>(key: string, value: T): void {
this.config.set(key, value);
}
get<T>(key: string): T | undefined {
return this.config.get(key) as T;
}
}Summary
| Aspect | Description |
|---|---|
| Purpose | Single instance with global access |
| Components | Private constructor, static instance, getInstance method |
| Benefits | Resource control, consistent shared state |
| Considerations | Can hinder testing, use resetInstance for tests |
Conclusion
The Singleton pattern is fundamental for managing shared resources in a controlled way. Use it wisely: it's powerful but can introduce global coupling if overused.
In the next article we'll explore the Factory Method Pattern, which delegates object creation to specialized subclasses.
Based on "Design Patterns: Elements of Reusable Object-Oriented Software" (Gang of Four).