How To Work With Singletons in JavaScript
Practical guide to the JavaScript singleton pattern: concepts, comparisons with globals, and three modern implementations (object literal, IIFE, ES6 module).
Drake Nguyen
Founder · System Architect
Introduction
The JavaScript singleton pattern ensures a class or object has only one shared instance throughout an application. Use this creational design pattern when a single, centralized resource such as a configuration object, logger, or cache is required. This guide explains what a singleton is, compares it to globals, and shows several safe, modern implementations you can use today.
Prerequisites
- Comfort with JavaScript syntax and functions.
- Familiarity with ES6 classes, modules, and closures helps when implementing different approaches.
What is the JavaScript singleton pattern?
A singleton creates or returns one shared instance of a type for the lifetime of a program. Unlike an ordinary global variable, a singleton typically controls access through methods and can encapsulate state and behavior. When considering singleton vs global variable in JavaScript, singletons encourage clearer APIs and encapsulation, while globals can lead to name collisions and harder-to-test code.
Key characteristics
- Single shared instance (shared instance, getInstance method semantics).
- Controlled creation and access, often via a factory or module.
- Useful for global state management when used sparingly.
How to implement the singleton pattern in JavaScript
Below are several common, practical implementations: object literal, closure/IIFE factory, and an ES6 class with Object.freeze. Choose the pattern that fits your project and testing strategy.
1. Simple object literal (module-like)
Quick and readable for small utilities. This follows the JavaScript module pattern style and is effectively a singleton when referenced directly.
const Logger = {
level: 'info',
log(msg) { console.log(`[${this.level}] ${msg}`); },
setLevel(lv) { this.level = lv; }
};
// Usage
Logger.log('App started');
2. IIFE / closure-based singleton with getInstance
This approach hides the constructor and returns a single instance via a getInstance method. It resembles the classic factory pattern JavaScript implementations and protects the internal class from direct instantiation.
const ConfigFactory = (function () {
function Config() {
this.values = { apiUrl: '/api' };
}
Config.prototype.get = function (key) {
return this.values[key];
};
let instance;
return {
getInstance() {
if (!instance) {
instance = new Config();
// Optional: remove direct access to constructor
// delete instance.constructor;
}
return instance;
}
};
})();
// Usage
const cfg = ConfigFactory.getInstance();
console.log(cfg.get('apiUrl'));
3. ES6 class with Object.freeze and module export (ES6 singleton)
This modern pattern creates a single instance and freezes it to prevent accidental mutation. Exporting the instance from an ES6 module is a common way to implement an ES6 module singleton pattern.
// logger.js
class LoggerClass {
constructor() {
this.level = 'info';
}
log(msg) { console.log(`[${this.level}] ${msg}`); }
setLevel(lv) { this.level = lv; }
}
const loggerInstance = Object.freeze(new LoggerClass());
export default loggerInstance;
// otherFile.js
// import logger from './logger.js';
// logger.log('Hello');
4. Singleton with module scope (ES6 module singleton example)
ES6 modules are singletons by nature: the module evaluation runs once and exported bindings remain shared. Exporting an initialized object is an idiomatic way to create a singleton:
// settings.js
export const settings = {
env: 'production',
get isProd() { return this.env === 'production'; }
};
// Usage elsewhere
// import { settings } from './settings.js';
// settings.env = 'development';
When to use the singleton pattern in JavaScript
- Use it for cross-cutting concerns such as logging, configuration, or a connection pool.
- Avoid singletons for mutable shared state in large apps where they make testing and concurrency harder.
- Prefer ES6 modules or dependency injection in complex systems instead of widespread singleton usage.
Trade-offs and best practices
- Testability: singletons can hide dependencies. Inject dependencies where possible.
- Immutability: Object.freeze can reduce accidental changes (JavaScript singleton Object.freeze example).
- Clarity: prefer named exports and clear factory/getInstance methods so intent is obvious.
- Alternatives: factory pattern JavaScript implementations, module pattern, or explicit dependency injection may be better choices.
Conclusion
The JavaScript singleton pattern is a useful tool for providing a single, shared instance of an object across an application. Use it deliberately—preferring module exports or frozen instances for reliability—and consider alternatives like the factory pattern or dependency injection when you need flexible, testable code. For more patterns, explore factory pattern JavaScript tutorials and state management approaches in modern frameworks.
Tip: When implementing singletons, document the intended lifecycle and mutability to reduce surprises for future maintainers.