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
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.