Low Level DesignSept 11, 20263 min read

Singleton Design Pattern

Deep dive into the Singleton Design Pattern with real-world examples

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

Real World Analogy

Think about the role of a President (or Prime Minister) in a country. A nation can have many citizens, many government officials, and many distinct government departments. However, at any given moment, there is only one President.

This role is a Singleton.

When the government needs the President to sign a bill, or meet a foreign leader, everyone references the current, single President. They don't spawn a "new President" for each task. The office itself (the pattern) manages who that single instance (the person) is.
Singleton
Singleton

Left: Without Singleton: Different clients directly instantiate the object they need. This results in multiple instances existing at once, each possibly having different internal data. Right: With Singleton: All clients must go through a static gatekeeper method. This checks if the single instance already exists. If it does, it returns it; if not, it creates it once and then returns it.

UML
UML
  • PresidentSingleton Class: This central box defines the structure.

  • -instance: PresidentSingleton (Static): A private, static variable that holds the reference to the single instance. The minus sign (-) means private. Static means it belongs to the class itself, not to objects of the class. It initializes to null.

  • -PresidentSingleton() (Private Constructor): This is critical. The constructor is private. This means external code cannot call new PresidentSingleton(). The pattern works by blocking the standard new keyword.

  • +getInstance(): PresidentSingleton (Public Static): This is the global access point. It is public, static. The plus sign (+) means public. The entire system uses this method to interact with the singleton. It manages the creation logic (if null, create; otherwise, return existing).


Working:

It involves the following steps:

  • Private Constructor: Prevents instantiation from outside the class
  • Static Variables: Holds the single instance of the class.
  • Public Static Method: Provides a global access point to get the instance. There are 2 ways of implementing it:
  1. Eager Loading
  2. Lazy Loading

Eager Loading (Early Initialization)

The resource is instantiated upfront as soon as the module/class is executed:

js
class DatabaseConnection{
	constructor() {
		console.log("DB Connection Initialized");
	}
	
	query(sql){
		return `Executing query: ${sql}`;
	}
}

class EagerDatabaseService {
	//Static Property initialized immediately
	static instance = new DatabaseConnection();
	
	static getInstance() {
		return this.instance;
	}
}

//Even before calling getInstance(), connection is already created
console.log("App Started");
const db = EagerDatabaseService.getInstance();
console.log(db.query("SELECT * FROM users"));

Lazy Loading

The resource is held as null until someone calls the accessor method, instantiating it only on demand:

js
class DatabaseConnection {
  constructor() {
    console.log("Database connection initialized!");
  }

  query(sql) {
    return `Executing query: ${sql}`;
  }
}

class LazyDatabaseService {
  static #instance = null; // Starts empty

  static getInstance() {
    if (!this.#instance) {
      console.log("Instance not found, creating now...");
      this.#instance = new DatabaseConnection();
    }
    return this.#instance;
  }
}

console.log("App started");
// Nothing is initialized yet!

// Instantiation happens here on the first request
const db1 = LazyDatabaseService.getInstance();

// Reuses the existing instance on subsequent requests
const db2 = LazyDatabaseService.getInstance();

console.log(db1 === db2); // true