Singleton: Why & How

Deep dive into the Singleton pattern. Learn why we need single instances for databases and loggers, and explore Lazy vs. Eager initialization.

April 22, 20247 min read1 / 4

Building on our foundation of Design Patterns, we start our journey with the most famous Creational pattern: the Singleton. As we discussed in the intro, Creational patterns focus on how objects are born. The Singleton pattern ensures that a class has only one instance across the entire system.

The Singleton Pattern: The "Why"

To truly understand Singleton, we won't start with the definition. Instead, we will look at a real-world problem that almost every backend engineer faces.

The Database Connection Problem

Imagine you are building a backend server (like a BookMyShow or an e-commerce app) that connects to a database.

Most of the time, your application server and your database are on different machines. To talk to the database, your application must create a connection over the network.

The Solution: Connection Pooling (The Taxi Analogy)

Imagine you have a hotel with 100 guests. Every time a guest wants to go to the airport, if you buy a new car, drive them there, and then crush the car at the airport, you would go bankrupt in one day! That is the "Naive Approach."

Instead, you buy 5 Taxis (this is your Pool).

  1. Guest 1 takes Taxi A to the airport and comes back.
  2. Taxi A is now free.
  3. Guest 2 takes the same Taxi A.

We only ever have 5 cars, but they can serve hundreds of guests because we reuse them. In coding, a "Connection Pool" is just a set of "Database Taxis" that stay open and wait for the next request to use them.

The Architectural Challenge: Too Many Managers?

Now, imagine our Hotel has different departments: the Reception, the Restaurant, and the Spa. Each department needs to send guests to the airport.

  • The Problem: If every department decides to buy its own "Pool of 5 Taxis," our hotel parking lot will quickly fill up with 15 cars. We are back to wasting money and space.
  • The Coding Equivalent: In a backend app, you have many "departments" like UserController, BookingController, and MovieController. If each one creates its own Database object, and each object creates its own pool of 5 connections, you suddenly have dozens of pools and hundreds of connections. You've crashed the database again!

This is where the Singleton Pattern comes in.

Instead of every department buying their own cars, the Hotel appoints One Garage Manager (The Singleton). No matter which department needs a taxi, they all call the same Manager, who gives them a car from the same pool.


Other Use Cases for Singleton

  1. Logging Service: You want all parts of your application to log to the same file or command line. Multiple loggers would be redundant and inefficient.
  2. Expensive Object Creation: Creating a connection or an authorized service might involve heavy network I/O. We want to do this once and reuse it.
  3. Stateless/Immutable Objects: If a class (like a UserService) has no attributes and only methods, there is no benefit to having multiple instances.

The 1,000 Request Challenge

A common question is: "If 1,000 users request their profile at the exact same time, does the Singleton process them one by one or all at once?"

The answer depends on the language, but the Singleton's role remains the same.

1. In Java (Multi-threaded)

When 1,000 requests hit a Java server, the server uses a Thread Pool. Each request is assigned to a separate thread.

  • Parallel Execution: Hundreds of threads might be executing the profile() method at the same time.
  • The Singleton Behavior: All 1,000 threads will call Database.getInstance(). Because the instance is static, they all point to the exact same memory address.
  • The Risk: If the Singleton isn't "Thread-Safe" (like our Attempt 1), those 1,000 threads might try to create the object simultaneously, causing a crash or multiple instances.

2. In Node.js (Single-threaded Event Loop)

When 1,000 requests hit a Node.js server, they enter the Event Loop.

  • Concurrent Execution: Node.js processes the "JavaScript part" of each request one by one, but very quickly. While it waits for the database to respond to User 1, it starts processing User 2.
  • The Singleton Behavior: Every request uses the same exported instance. Since only one piece of JavaScript runs at a time, there is zero risk of two requests creating two different Singletons.

Why use a Singleton if it's shared?

Whether the server is "parallel" (Java) or "sequential" (Node), the Singleton ensures that all 1,000 requests share the same connection pool. Instead of 1,000 requests opening 1,000 separate connections, they all go through the same "Manager" which uses a small, efficient pool of 5 or 10 connections.


Implementing Singleton: The Evolution

Attempt 1: The Naive Implementation (Lazy Initialization)

To prevent multiple instances, we must first hide the constructor by making it private. We then provide a static method to access the instance.

class Database { private static instance: Database | null = null; private constructor() { console.log("Initializing Database Connection Pool..."); } public static getInstance(): Database { if (this.instance === null) { this.instance = new Database(); } return this.instance; } }

This is called Lazy Initialization because we only create the object when it is actually needed.

[!TIP] Try it yourself: You can practice building the lazy initialization logic in our interactive code lab at the bottom of this page.

The Problem: Concurrency & Multithreading

In a multi-threaded environment (like a Java server), this breaks. If two threads, James and Denver, call getInstance() at the exact same millisecond, both might see instance == null and both will execute new Database(). Your Singleton is broken.

Attempt 2: Eager Initialization

To solve the concurrency problem simply, we can use Eager Initialization, where the instance is created at the moment the class is loaded.

class Database { private static instance: Database = new Database(); private constructor() { } public static getInstance(): Database { return Database.instance; } }

Pros & Cons of Eager Initialization

  • Pros: Thread-safe by default (the system handles class loading). Simple code.
  • Cons: No runtime parameters (you can't take user input for the URL). Slow startup if many singletons are created at once.

[!TIP] Watch & Learn: If you're still finding these concepts a bit tricky, I highly recommend watching this deep dive on the Singleton Pattern. While I didn't create this video myself, it is an excellent visual supplement to this blog!

Summary & What's Next

Singleton ensures a class has only one instance. While Lazy Initialization is efficient, it's thread-unsafe in languages like Java, though it behaves differently in single-threaded environments.

In the next part, we will look at Singleton in JavaScript and TypeScript and why Node.js developers almost never need to worry about the complex locking patterns used in Java!

Practice what you just read.

Lab: Singleton Memory VisualizerLab: The Lazy Initialization RitualQuiz: Singleton Pattern Foundations
3 exercises