Singleton Pattern

intermediate 2-4 YOE lld design-pattern creational

The Singleton pattern guarantees that a class has only one instance and provides a global access point to it. That’s it. One object, shared everywhere.

Think of it like the president of a country. There’s only one at a time. Everyone refers to the same person. We don’t create a new president every time someone needs to talk to one.

The Problem It Solves

Some things should only exist once in our application:

  • Database connection pool — we don’t want 50 separate pools fighting for connections
  • Logger — all parts of the app should log to the same place
  • Config manager — one source of truth for settings

Without Singleton, any part of the code could do new DatabasePool() and accidentally create duplicates. Wasteful and buggy.

How It Works

Singleton Structure
Singleton
- static instance: Singleton
- private constructor()
+ static getInstance(): Singleton
Constructor is private → no one can call new Singleton()
getInstance() creates the instance on first call, returns it on every subsequent call

The trick: make the constructor private so nobody can call new. Then expose a static method that creates the instance only once and returns it every time.

Basic Implementation

class DatabaseConnection:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.connection = "Connected to DB"
            print("Creating new DB connection...")
        return cls._instance

# Both variables point to the SAME object
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2)  # True
class DatabaseConnection {
  static #instance = null;

  constructor() {
    if (DatabaseConnection.#instance) {
      return DatabaseConnection.#instance;
    }
    this.connection = "Connected to DB";
    console.log("Creating new DB connection...");
    DatabaseConnection.#instance = this;
  }

  static getInstance() {
    if (!DatabaseConnection.#instance) {
      DatabaseConnection.#instance = new DatabaseConnection();
    }
    return DatabaseConnection.#instance;
  }
}

const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();
console.log(db1 === db2); // true
public class DatabaseConnection {
    private static DatabaseConnection instance;
    private String connection;

    // Private constructor -- no one can call new
    private DatabaseConnection() {
        this.connection = "Connected to DB";
        System.out.println("Creating new DB connection...");
    }

    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }
}

// Usage
// DatabaseConnection db1 = DatabaseConnection.getInstance();
// DatabaseConnection db2 = DatabaseConnection.getInstance();
// db1 == db2 → true

Thread-Safe Singleton (Interview Favorite!)

The basic version has a problem. If two threads call getInstance() at the exact same time, both might see instance == null and create two objects. Not good.

import threading

class ThreadSafeSingleton:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                # Double-check after acquiring the lock
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance
// In JS, the event loop is single-threaded
// so we don't truly need thread safety.
// But in Node.js worker threads or for good practice:
class ThreadSafeSingleton {
  static #instance = null;

  static getInstance() {
    // JS is single-threaded, so this is already safe
    // But the pattern is still good to know for interviews
    if (!ThreadSafeSingleton.#instance) {
      ThreadSafeSingleton.#instance = new ThreadSafeSingleton();
    }
    return ThreadSafeSingleton.#instance;
  }
}
public class ThreadSafeSingleton {
    // volatile ensures visibility across threads
    private static volatile ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {}

    public static ThreadSafeSingleton getInstance() {
        if (instance == null) {                    // First check (no lock)
            synchronized (ThreadSafeSingleton.class) {
                if (instance == null) {            // Second check (with lock)
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
    }
}

This is called double-checked locking. We check twice — once without the lock (fast path) and once with the lock (safe path). Interviewers love asking about this.

When to Use

  • Database connection pools — one pool shared across the app
  • Loggers — centralized logging
  • Config/settings — single source of truth
  • Cache managers — one shared cache
  • Thread pools — manage threads from one place

When NOT to Use

  • When it makes testing hard — singletons are essentially global state, and global state makes unit tests flaky
  • When we need multiple instances later — refactoring away from singleton is painful
  • When it hides dependencies — if every class secretly depends on a singleton, the code becomes hard to understand

In simple language, Singleton is a box that only ever has one item in it. Everyone in the app reaches into the same box. Super useful for shared resources, but don’t overuse it — global state can bite us during testing.