Singleton Pattern

Med

The Singleton Pattern ensures a class or module produces exactly one instance throughout the application lifetime. It provides a global access point to that instance, commonly used for configuration stores, connection pools, and logging services.

Interactive Visualization

Creational Patterns

IIFE creates private scope, returns public API via closure
returnscloses overcallsIIFE Scopeprivate varsPublic APIConsumer
1 / 3

Understanding Singleton Pattern

The Singleton Pattern restricts instantiation of a class or module to a single object and provides a global point of access. In JavaScript, this pattern appears everywhere: the window object, the module cache in Node.js, and any configuration object shared across an application.

The classic closure-based implementation stores the instance in a private variable and exposes a getInstance() method. The class-based approach overrides the constructor to return the existing instance. However, the most idiomatic JavaScript singleton is simply a module — because ES modules are evaluated exactly once and the result is cached, any variable at module scope is inherently a singleton.

The pattern is powerful but comes with trade-offs. Global state makes code harder to test because one test can inadvertently affect another. It also creates hidden dependencies — instead of receiving a dependency explicitly, code reaches out to a global. For this reason, many teams prefer dependency injection over hard singletons. In interviews, demonstrating awareness of both the benefits (simplicity for shared resources) and the costs (testability, coupling) shows senior-level judgment.

Key Points

  • Guarantees only one instance of a class or object exists at runtime
  • Provides a global access point via a static method or module export
  • Lazy initialization — the instance is created on first access, not at import time
  • In JavaScript, ES modules are inherently singletons because modules are cached after first evaluation
  • Useful for shared resources: config, logger, database connection pool, caches
  • Can complicate testing because global state leaks between tests
  • The Proxy-based singleton can intercept construction to enforce the single-instance rule

Code Examples

Closure-Based Singleton

const Database = (function () {
  let instance = null;

  function createConnection() {
    return {
      host: 'localhost',
      query(sql) { console.log('Executing:', sql); },
    };
  }

  return {
    getInstance() {
      if (!instance) {
        instance = createConnection();
      }
      return instance;
    },
  };
})();

const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true — same instance

The closure holds the single instance. getInstance returns the existing one or creates it on first call.

Class-Based Singleton

class Logger {
  static #instance = null;

  #logs = [];

  constructor() {
    if (Logger.#instance) {
      return Logger.#instance;
    }
    Logger.#instance = this;
  }

  log(message) {
    this.#logs.push({ message, timestamp: Date.now() });
    console.log('[LOG]', message);
  }

  getHistory() { return [...this.#logs]; }
}

const a = new Logger();
const b = new Logger();
console.log(a === b); // true

The constructor checks for an existing instance and returns it, preventing multiple instantiations.

ES Module Singleton (Idiomatic JS)

// config.js — module-level singleton
let settings = null;

function loadSettings() {
  return {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retries: 3,
  };
}

export function getConfig() {
  if (!settings) {
    settings = loadSettings();
  }
  return settings;
}

// Any file that imports getConfig() shares the same settings

ES modules are evaluated once and cached. Module-scoped variables naturally behave as singletons.

Common Mistakes

  • Using singletons for everything — they introduce hidden global state and tight coupling
  • Forgetting that singletons make unit testing harder because shared state leaks between tests
  • Not providing a reset method for testing purposes
  • Assuming ES module caching works across different bundler entry points — it depends on the build tool
  • Creating race conditions when lazy initialization involves async operations

Interview Tips

  • Explain when singletons are appropriate (shared resources, config) vs harmful (application state)
  • Know that ES modules are inherently singletons — no extra pattern needed
  • Discuss the testing trade-off: convenience vs global state coupling
  • Mention dependency injection as the testable alternative to hard singletons

Related Concepts