Introduction
The Singleton pattern ensures a class has only one instance and provides global access to it. While controversial in some circles, Singletons are useful for shared resources like configurations, connection pools, and logging services.
Key Concepts
Singleton: A class that guarantees only one instance exists.
Lazy Initialization: Creating the instance only when first needed.
Global Access Point: Single entry point to the instance.
Real World Context
Database connections, application configuration, logging services, caches—Singletons manage shared resources efficiently. Many frameworks use Singletons internally.
Deep Dive
Classic Implementation (IIFE)
The IIFE approach wraps a private instance variable inside a closure, exposing only a getInstance() method that creates the instance lazily on first call:
javascriptconst Singleton = (function() { let instance; function createInstance() { return { config: {}, setConfig(key, value) { this.config[key] = value; }, getConfig(key) { return this.config[key]; } }; } return { getInstance() { if (!instance) { instance = createInstance(); } return instance; } }; })(); const a = Singleton.getInstance(); const b = Singleton.getInstance(); a === b; // true - same instance!
Both calls to getInstance() return the exact same object reference, proving that only one instance is ever created.
ES6 Class Implementation
Using a private static field (#instance), this class-based approach stores the single instance and short-circuits construction if one already exists:
javascriptclass Database { static #instance = null; constructor() { if (Database.#instance) { return Database.#instance; } this.connection = null; Database.#instance = this; } static getInstance() { if (!Database.#instance) { Database.#instance = new Database(); } return Database.#instance; } connect(config) { if (!this.connection) { this.connection = createConnection(config); } return this.connection; } } const db1 = Database.getInstance(); const db2 = Database.getInstance(); db1 === db2; // true
The private static field #instance ensures that even calling new Database() directly returns the existing instance, making the pattern bulletproof.
Module Pattern (Simplest)
ES modules are inherently singletons because the module is evaluated once and every import receives the same reference:
javascript// config.js const config = { apiUrl: 'https://api.example.com', timeout: 5000 }; export default config; // ES modules are singletons by nature! // Every import gets the same object
This is the simplest Singleton in JavaScript. No factory method is needed because the module system guarantees a single evaluation.
Common Pitfalls
- Global state issues: Singletons can make testing difficult.
- Hidden dependencies: Code depends on global state.
- Thread safety (Node.js): Ensure atomic initialization.
Best Practices
- Use ES modules for simple cases: Natural singletons.
- Consider dependency injection: Instead of global access.
- Make singletons configurable: Allow resetting for tests.
- Document singleton dependencies: Make usage explicit.
Summary
Singletons ensure one instance exists globally. ES modules are natural singletons. Use for shared resources like config, logging, and connection pools. Be aware of testing challenges with global state.
Code Examples
class Database {
static #instance = null;
constructor() {
if (Database.#instance) return Database.#instance;
this.connection = null;
Database.#instance = this;
}
static getInstance() {
if (!Database.#instance) Database.#instance = new Database();
return Database.#instance;
}
}
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true