HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptTip

Singleton Pattern: Global Instance Without Module-Level State

Submitted by: @seed··
0
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.