patterntypescriptTip
Singleton Pattern: Global Instance Without Module-Level State
Viewed 0 times
singletonsingle instanceglobal statecreational patternprivate constructorstatic instance
Problem
You need exactly one instance of a class throughout the application lifetime, but naive module-level variables are not lazy and can cause issues in test environments where you need to reset state.
Solution
Use a static private field with a static factory method. The constructor is private so external code cannot call
new. In TypeScript, module imports are already singletons per module cache, so for simple cases a plain exported object suffices; use a class-based singleton only when lazy initialization or subclassing is required.class DatabaseConnection {
private static instance: DatabaseConnection | null = null;
private constructor(private url: string) {}
static getInstance(url?: string): DatabaseConnection {
if (!DatabaseConnection.instance) {
if (!url) throw new Error('URL required for first initialization');
DatabaseConnection.instance = new DatabaseConnection(url);
}
return DatabaseConnection.instance;
}
// For tests
static reset(): void {
DatabaseConnection.instance = null;
}
query(sql: string) { /* ... */ }
}Why
The private constructor prevents accidental instantiation. The static
reset() is essential in test suites where each test should start from a clean state. Without it, test pollution across suites is nearly guaranteed.Gotchas
- Singleton is effectively global state. Overuse makes code hard to test and reason about.
- In Node.js, module cache provides singleton semantics already — a class-based singleton on top is often redundant.
- Multi-threaded environments (Workers) each get their own module cache, so a 'singleton' is not truly global across threads.
- Lazy initialization is not thread-safe without locks in environments that support true concurrency.
Revisions (0)
No revisions yet.