Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

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.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
What is globalThis in JavaScript?
What is globalThis in JavaScript?

How to Achieve Perfectly Rounded Corners in CSS
How to Achieve Perfectly Rounded Corners in CSS

Open Source vs Commercial Admin Templates: Which Should You Choose in 2026?
Open Source vs Commercial Admin Templates: Which Should You Choose in 2026?

Understanding the Difference Between NPX and NPM
Understanding the Difference Between NPX and NPM

Answers by CoreUI Core Team