Skip to main content
HomeBlogsContactLinks
RSS

Singleton Pattern - One Instance to Rule Them All

04 May 2026

  • Learning
  • Tech
  • Engineering
  • Design Pattern

Table of Contents

  • Prerequisites
  • Introduction
  • What is Singleton Pattern
  • Problem in Booking System
  • Solution
  • Implementation
  • Real Usage
  • Pros and Cons
  • When not to use it
  • Conclusion
  • Reference

Prerequisites

  • Basic understanding of OOP concepts
  • Basic understanding of UML diagrams
  • Basic TypeScript knowledge

If you're not familiar with the first two points, this blog might not be suitable for you. Otherwise, you're good to go. The examples are intentionally simple and can be easily replicated in other languages.

Introduction

Hey devs, welcome back to the design pattern series!

So far in our Booking System, we've covered all five Creational patterns:

  • We used Factory Method to create the right payment processor based on type
  • We used Abstract Factory to build entire booking families (Standard, Holiday, Corporate)
  • We used Builder to construct complex bookings step by step
  • We used Prototype to clone existing bookings instead of recreating them from scratch

Today we're going back to look at the one pattern we skipped very first one on the Creational list. The Singleton Pattern.

It's often called the simplest pattern. And in some ways, it is. But simple doesn't mean trivial. Get it wrong and you'll end up with bugs that are surprisingly hard to trace.

Let's break it down through our Booking System.

What is Singleton Pattern

Singleton is a creational design pattern that ensures a class has only one instance and provides a global access point to that instance.

That's it. Two rules:

  1. Only one instance should ever exist
  2. That instance should be accessible from anywhere in the codebase

Singleton concept — one key, many users One instance. Everyone shares it.

The pattern controls instantiation. The class itself is responsible for making sure no second copy ever gets created. And it hands out the same instance every time someone asks for one.

Problem in Booking System

Let's think about our AvailabilityCache.

The availability cache stores which rooms, seats, or vehicles are currently free. Every time a user searches for available resources, the system hits this cache instead of going straight to the database. It's a performance layer and a critical one.

Now imagine what happens if multiple parts of the system each create their own instance of AvailabilityCache.

Multiple cache instances causing confusion Three caches. Three different versions of the truth. Which one is right?

what goes wrong here?

Stale data across instances One part of the system marks Room 101 as booked. But another part of the system is reading from its own separate cache instance that hasn't been updated. Room 101 looks available there. Double booking incoming.

Wasted memory Each instance holds its own copy of the entire availability dataset. That is not a small object. Multiply it by however many services are spinning up their own cache, and you have got a memory problem nobody asked for.

No single source of truth When availability changes, a booking is confirmed, or a cancellation comes in - only one cache gets the update. The rest stay out of sync. Your system is now lying to itself.

Without Singleton:

class AvailabilityCache {
  private cache: Map<string, boolean> = new Map();

  isAvailable(resourceId: string): boolean {
    return this.cache.get(resourceId) ?? true;
  }

  markAsBooked(resourceId: string): void {
    this.cache.set(resourceId, false);
  }
}

// Different parts of the system doing this independently
const cacheA = new AvailabilityCache(); // used by SearchService
const cacheB = new AvailabilityCache(); // used by BookingService
const cacheC = new AvailabilityCache(); // used by AdminService

// cacheA says Room 101 is available
// cacheB just marked Room 101 as booked
// cacheA still says available — race condition territory

Without Singleton vs With Singleton — availability cache Left: three caches, three sources of truth. Right: one cache, one source of truth.

This is exactly the problem the Singleton pattern is designed to solve.

Solution

The fix is straightforward. Make AvailabilityCache responsible for its own instantiation. Give it a private constructor so nothing outside can call new AvailabilityCache(). Then expose a static method that always returns the same instance.

Key Idea

  • The class controls its own lifecycle
  • First call creates the instance
  • Every subsequent call returns the same one
  • No one outside can accidentally create a second copy

Singleton as a controlled vending machine Everyone uses the same machine. Everyone gets the same item.

Implementation

Basic Singleton

class AvailabilityCache {
  private static instance: AvailabilityCache | null = null;
  private cache: Map<string, boolean> = new Map();

  private constructor() {
    // Private constructor - prevents direct instantiation
    // Initialize your cache here if needed
  }

  static getInstance(): AvailabilityCache {
    if (!AvailabilityCache.instance) {
      AvailabilityCache.instance = new AvailabilityCache();
    }
    return AvailabilityCache.instance;
  }

  isAvailable(resourceId: string): boolean {
    return this.cache.get(resourceId) ?? true;
  }

  markAsBooked(resourceId: string): void {
    this.cache.set(resourceId, false);
  }

  markAsAvailable(resourceId: string): void {
    this.cache.set(resourceId, true);
  }

  getSnapshot(): Map<string, boolean> {
    return new Map(this.cache);
  }
}

Now no matter where in the system you call AvailabilityCache.getInstance(), you always get the exact same object back.

// SearchService
const cache = AvailabilityCache.getInstance();
cache.isAvailable("room-101"); // true

// BookingService — same instance
const cache = AvailabilityCache.getInstance();
cache.markAsBooked("room-101");

// SearchService checks again — sees the update
const cache = AvailabilityCache.getInstance();
cache.isAvailable("room-101"); // false — correctly updated

UML Structure

Singleton UML class diagram The class holds a reference to itself. That is the whole trick.

A Note on Thread Safety

In a single-threaded environment like a typical Node.js process, the basic implementation above is completely fine. Node.js runs on a single thread, so two calls cannot race to create two instances simultaneously.

But if you are ever working in a multi-threaded environment (Java, C#, Go with goroutines), you need to think about thread safety. Two threads could both reach the if (!instance) check at the same time before either has created the instance, and both end up creating one. The fix there is a lock or a double-checked locking pattern.

Since we are in TypeScript/Node.js here, we don't have that concern. Just good to know it exists.

Real Usage

1. AvailabilityCache

The main example we've been building. Every service in the booking system shares one cache.

class SearchService {
  private cache = AvailabilityCache.getInstance();

  findAvailableRooms(date: string): string[] {
    return roomIds.filter(id => this.cache.isAvailable(id));
  }
}

class BookingService {
  private cache = AvailabilityCache.getInstance();

  confirmBooking(resourceId: string): void {
    // Process payment, save to DB...
    this.cache.markAsBooked(resourceId); // Same instance updated
  }
}

When BookingService marks a room as booked, SearchService immediately sees it gone from available results. No sync required.

2. BookingConfig

Configuration is another classic Singleton use case. Your system reads environment variables, feature flags, and settings once at startup. You want the same config everywhere, and you definitely don't want it re-read from disk or env on every service instantiation.

class BookingConfig {
  private static instance: BookingConfig | null = null;

  readonly maxBookingsPerUser: number;
  readonly cancellationWindowHours: number;
  readonly supportedCurrencies: string[];

  private constructor() {
    this.maxBookingsPerUser = parseInt(process.env.MAX_BOOKINGS ?? "5");
    this.cancellationWindowHours = parseInt(process.env.CANCEL_WINDOW ?? "24");
    this.supportedCurrencies = (process.env.CURRENCIES ?? "USD,EUR,GBP").split(",");
  }

  static getInstance(): BookingConfig {
    if (!BookingConfig.instance) {
      BookingConfig.instance = new BookingConfig();
    }
    return BookingConfig.instance;
  }
}
// Anywhere in the system
const config = BookingConfig.getInstance();
if (booking.currency && !config.supportedCurrencies.includes(booking.currency)) {
  throw new Error("Unsupported currency");
}

Singleton usage across services in the booking system Both Singletons are shared across all services. One call, same instance every time.

Pros and Cons

Pros

BenefitWhy it matters
Single source of truthAll parts of the system read and write to the same object
Controlled instantiationThe class manages its own lifecycle, no accidents
Memory efficientOne instance in memory instead of N copies
Global accessNo need to pass the object around through every layer
Lazy initializationInstance is created only when first needed, not at startup

Cons

DrawbackWhy it matters
Hard to testGlobal state makes unit tests interfere with each other
Hidden dependenciesCode that uses getInstance() hides its dependency from the outside
Violates Single ResponsibilityThe class manages both its own logic and its own instantiation
Difficult to subclassPrivate constructor makes extending the class complicated
Can mask design problemsSometimes a Singleton is used where proper dependency injection would be cleaner

When not to use it

  • When the object has no shared state worth protecting. If every caller can safely have their own instance, there is no reason for Singleton.
  • When you are writing code that needs to be unit tested easily. Global state is the enemy of isolated tests. Two tests that both write to the same Singleton cache will interfere with each other unless you reset state manually between runs.
  • When the "single instance" rule is not actually a business requirement. Sometimes developers reach for Singleton out of habit, not necessity. Ask yourself does it actually break something if two instances exist?
  • When dependency injection is already available. If your framework supports DI (NestJS, Spring, ASP.NET Core), use it. The DI container handles instance scope for you in a much more testable and explicit way.
  • When you are running in a distributed system. A Singleton is single per process, not single per cluster. If you have 10 instances of your service running, each has its own Singleton. At that scale, you need something like Redis for shared state not an in-memory Singleton.

The Singleton pattern solves the "one instance per process" problem. It does not solve the "one instance across all servers" problem. Know the difference before reaching for it.

Conclusion

  • Singleton Pattern = one instance, globally accessible, class-controlled
  • Perfect for shared infrastructure objects like AvailabilityCache and BookingConfig
  • The private constructor is what enforces the guarantee nothing outside can bypass it
  • In Node.js, basic lazy initialization is safe multi-threaded environments need extra care
  • The biggest trap is overusing it when in doubt, prefer dependency injection

Ok devs, that's it for today. I tried my best to explain this pattern. We have wrapped up all five Creational patterns in our Booking System:

PatternRole in Booking System
SingletonOne shared AvailabilityCache and BookingConfig
Factory MethodCreate the right PaymentProcessor by type
Abstract FactoryBuild entire booking families (Standard, Holiday, Corporate)
BuilderConstruct complex bookings step by step
PrototypeClone existing bookings instead of recreating from scratch

Next up, we move into Structural Patterns. The first one is the Adapter Pattern and we'll use it to wrap a third-party payment API so it fits cleanly into our IPaymentProcessor interface without touching the core system. See you there!

Code, learn, refactor, repeat

Reference

  • Refactoring Guru - Singleton Pattern
  • Gang of Four - Design Patterns: Elements of Reusable Object-Oriented Software

Comments

Add a new comment

Stay Connected

GitHub •LinkedIn •X •Daily.dev •Email

© 2026 Chiristo. Feel free to share or reference this post with proper credit