How to implement singleton pattern in JavaScript
The singleton pattern ensures a class has only one instance and provides global access to it. As the creator of CoreUI with over 25 years of JavaScript experience since 2000, I’ve used singletons for database connections, configuration managers, and logging services in large-scale applications. The most effective approach uses ES6 classes with static properties or closures to enforce single instantiation. This provides controlled access to shared resources.
Implement singleton using ES6 class with static instance.
class Database {
constructor() {
if (Database.instance) {
return Database.instance
}
this.connection = null
Database.instance = this
}
connect() {
if (!this.connection) {
this.connection = 'Connected to DB'
console.log(this.connection)
}
}
query(sql) {
console.log('Executing:', sql)
}
}
const db1 = new Database()
const db2 = new Database()
console.log(db1 === db2) // true
The constructor checks if an instance already exists. If it does, that instance is returned. The static property stores the single instance. Multiple instantiations return the same object.
Using Module Pattern
Create singleton with closure and IIFE.
const Logger = (function() {
let instance
function createInstance() {
return {
logs: [],
log(message) {
this.logs.push({ message, timestamp: Date.now() })
console.log(message)
},
getLogs() {
return this.logs
}
}
}
return {
getInstance() {
if (!instance) {
instance = createInstance()
}
return instance
}
}
})()
const logger1 = Logger.getInstance()
const logger2 = Logger.getInstance()
console.log(logger1 === logger2) // true
The IIFE creates a private scope. The instance variable is private. The getInstance method controls access. This pattern provides better encapsulation.
Using ES6 Module
Leverage ES6 module system for natural singletons.
// config.js
class Config {
constructor() {
this.settings = {
apiUrl: 'https://api.example.com',
timeout: 5000
}
}
get(key) {
return this.settings[key]
}
set(key, value) {
this.settings[key] = value
}
}
export default new Config()
// app.js
import config from './config.js'
config.set('apiUrl', 'https://new-api.com')
console.log(config.get('apiUrl'))
ES6 modules are evaluated once. Exporting an instance creates a natural singleton. All imports reference the same object. This is the simplest approach for modern JavaScript.
Implementing Lazy Initialization
Create instance only when first accessed.
class Cache {
static instance = null
constructor() {
if (Cache.instance) {
return Cache.instance
}
this.data = new Map()
Cache.instance = this
}
static getInstance() {
if (!Cache.instance) {
Cache.instance = new Cache()
}
return Cache.instance
}
set(key, value) {
this.data.set(key, value)
}
get(key) {
return this.data.get(key)
}
}
const cache = Cache.getInstance()
cache.set('user', { id: 1, name: 'John' })
The getInstance method creates the instance on first call. Subsequent calls return the existing instance. This avoids unnecessary instantiation if the singleton is never used.
Thread-Safe Singleton with Proxy
Use Proxy to prevent direct instantiation.
class ApiClient {
constructor() {
this.baseUrl = 'https://api.example.com'
}
get(endpoint) {
return fetch(`${this.baseUrl}${endpoint}`)
}
}
const handler = {
construct(target, args) {
if (!handler.instance) {
handler.instance = new target(...args)
}
return handler.instance
}
}
const ApiClientSingleton = new Proxy(ApiClient, handler)
const api1 = new ApiClientSingleton()
const api2 = new ApiClientSingleton()
console.log(api1 === api2) // true
The Proxy intercepts constructor calls. The handler stores the single instance. This prevents accidental multiple instantiation. Users can still use new keyword naturally.
Best Practice Note
This is the same singleton pattern we use in CoreUI for managing global state and shared services. Use singletons sparingly - they create global state which can make testing difficult. Prefer dependency injection for better testability. Singletons work well for logging, configuration, and connection pools. In modern applications, consider using React Context, Vue provide/inject, or Angular services instead of raw singletons. The ES6 module approach is simplest for most cases. Avoid singletons for business logic - they make code harder to test and reason about.



