Low-level design2 min

Singleton Pattern

Type: Creational Pattern
Relevance: Extremely high (very common in interviews), but often considered an anti-pattern in modern development if overused, because it introduces global state.

The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance.

Real-Life Analogy

Think of the President of a Country. A country can only have one active President at a time. Regardless of who asks to speak to the President, or from where they ask, they are always directed to the exact same person.

The Problem

  1. Global Access: You need access to some shared resource (like a database connection pool, a logger, or a configuration manager) from anywhere in your application.
  2. Instance Control: If you create two database connection managers, they might step on each other's toes or waste memory. You strictly need exactly one.

How to Implement Singleton

To create a Singleton:

  1. Make the default constructor private so other objects cannot use the new operator.
  2. Create a static method (often called getInstance()) that acts as a constructor. This method checks if an instance already exists. If not, it creates it. If it does, it returns the existing one.

Example in Code

typescript
class DatabaseConnection {
  // 1. Static variable that holds the single instance
  private static instance: DatabaseConnection;

  // 2. Private constructor prevents instantiation from outside
  private constructor() {
    console.log("Initializing database connection...");
  }

  // 3. Public static method to get the instance
  public static getInstance(): DatabaseConnection {
    if (!DatabaseConnection.instance) {
      DatabaseConnection.instance = new DatabaseConnection();
    }
    return DatabaseConnection.instance;
  }

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

// Usage
const db1 = DatabaseConnection.getInstance(); // Prints: "Initializing..."
const db2 = DatabaseConnection.getInstance(); // Does not print anything

console.log(db1 === db2); // true! They are the exact same instance in memory.

Class Diagram

Thread-Safety (Important for Java/C++ etc.)

In multi-threaded languages, two threads might call getInstance() at the exact same millisecond when the instance is null. Both might bypass the if (!instance) check and create two instances! To fix this, you must use locking (e.g., synchronized blocks in Java or Double-Checked Locking).

Why is it an Anti-Pattern?

  • Hidden Dependencies: It hides dependencies across your application. Instead of injecting a database into a class, the class just magically summons it out of thin air via Database.getInstance().
  • Testing Nightmare: Because it's global state, tests can pollute each other. If Test A changes a setting in the Singleton, Test B might fail because it reads that modified setting.