Singleton Pattern

⭐ Interview Importance: HIGH
⏱️ Revision Time: 1 min

Concept

Category: Creational Pattern

The Singleton Pattern ensures that a class has exactly one instance in the entire application, and provides a global point of access to that instance.

Imagine a Database Connection Pool or a Global Configuration Manager.
If you create a new Database Connection object every single time a user clicks a button, you will open 10,000 parallel connections to your SQL server, instantly crashing it. You strictly want one global Database Connection object that every file in your codebase shares.

The Mental Model

Think of a country’s President.
A country can only have one President at a time. Regardless of which citizen wants to talk to the President, they must all go to the exact same physical person. You cannot instantiate new President() in your backyard.

Implementation

To mathematically prevent developers from typing new Singleton(), we must make the constructor completely private.
We then create a static method (usually called getInstance()) that checks if the instance already exists. If it does, it returns it. If it doesn’t, it internally calls the private constructor once, saves it, and returns it.

class DatabaseConnection {
    // 1. The internal static variable that holds the ONE true instance
    private static instance: DatabaseConnection;

    // 2. The constructor MUST be private! 
    // This physically prevents anyone from typing `new DatabaseConnection()`
    private constructor() {
        console.log("Initializing the massive Database Connection...");
        // Heavy lifting (opening TCP ports, authenticating, etc.)
    }

    // 3. The Global Access Point
    public static getInstance(): DatabaseConnection {
        // If the instance doesn't exist yet, create it! (Lazy Initialization)
        if (!DatabaseConnection.instance) {
            DatabaseConnection.instance = new DatabaseConnection();
        }

        // Return the one true instance
        return DatabaseConnection.instance;
    }

    public query(sql: string) {
        console.log(`Executing query: ${sql}`);
    }
}

// Usage:
// const db = new DatabaseConnection(); // ❌ ERROR! Constructor is private.

const db1 = DatabaseConnection.getInstance(); // Prints: "Initializing..."
const db2 = DatabaseConnection.getInstance(); // Returns instantly!

console.log(db1 === db2); // true! They are the exact same physical object in RAM.

The Anti-Pattern Debate

In modern software engineering, the Singleton is heavily criticized and often considered an Anti-Pattern.

Why?

  1. Global State: Singletons are essentially glorified global variables. If db1 mutates the state of the Singleton, db2 in a completely different file is secretly affected, causing absolute nightmares for debugging.
  2. Testing Nightmares: Because Singletons persist across the entire application lifecycle, if you run Unit Test A (which modifies the Singleton), Unit Test B will fail because the Singleton didn’t reset! You cannot easily “mock” or reset a hardcoded Singleton.
  3. Tight Coupling: Any file that imports DatabaseConnection.getInstance() is permanently hardcoded to that exact class.

The Modern Alternative: Dependency Injection (DI).
Instead of the class restricting itself to one instance, we let a modern framework (like Spring Boot, NestJS, or React Context) instantiate the class exactly once when the server boots up, and then “Inject” that single instance into the constructors of whatever files need it. This gives us the benefits of a Singleton, without the toxic global state or testing nightmares!

Interview Questions

Q: What is “Thread-Safe Lazy Initialization” in a Singleton?
A: In languages with true multi-threading (like Java or C++), if Thread A and Thread B call getInstance() at the exact same millisecond, they might both pass the if (!instance) check simultaneously, creating TWO physical Singletons and destroying the pattern! To fix this, the getInstance() method must use a synchronized lock (Mutex), ensuring only one thread can execute the initialization block at a time. (This is not an issue in JavaScript due to its single-threaded Event Loop).

Q: Can I just use a simple Object Literal const db = {} instead of a Singleton class in JavaScript?
A: Yes! In Node.js, const db = {} ; export default db; perfectly mimics a Singleton. Because Node.js physically caches modules the first time they are require()’d, any other file that imports db will receive the exact same cached object reference in memory. This is how 99% of “Singletons” are actually written in modern JavaScript/TypeScript.