Low Level Design
Sept 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.terminal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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"));
➜~js
UTF-824 lines● ready

Lazy Loading

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

js.terminal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
➜~js
UTF-832 lines● ready