Design Patterns Singleton

The Singleton pattern ensures that a class has exactly one instance throughout the entire life of an application, and provides a single point of access to that instance. No matter how many times you ask for it, you always get back the same object.

The Real-World Analogy

A country has exactly one president at a time. Citizens do not create a new president every time they need one — they always refer to the same person currently in office. The Singleton pattern works identically: your code always refers back to one shared object.

Problem Without Singleton

App starts
│
├── Module A creates ConfigManager → loads settings file ✓
├── Module B creates ConfigManager → loads settings file again ✗ (wasteful)
├── Module C creates ConfigManager → loads settings file again ✗ (wasteful)
│
Result: Three different ConfigManager objects, possibly with different values.
        Changes in one object are invisible to the others.

Solution With Singleton

App starts
│
├── Module A asks for ConfigManager
│       └── No instance exists yet → creates one, stores it
│
├── Module B asks for ConfigManager
│       └── Instance already exists → returns the same one ✓
│
├── Module C asks for ConfigManager
│       └── Instance already exists → returns the same one ✓
│
Result: All modules share one ConfigManager. Changes are visible everywhere.

UML Diagram

┌─────────────────────────────────┐
│           Singleton             │
├─────────────────────────────────┤
│ - instance: Singleton           │  ← the one and only object (static)
│ - data: string                  │
├─────────────────────────────────┤
│ - Singleton()                   │  ← private constructor (blocks "new")
│ + getInstance(): Singleton      │  ← the only way to get the object
│ + getData(): string             │
└─────────────────────────────────┘

Why the Constructor Is Private

Making the constructor private blocks any outside code from writing new Singleton(). The only door in is through getInstance(), which checks whether an instance already exists before creating one.

How getInstance() Works (Step-by-Step Flow)

Call getInstance()
        │
        ▼
  Is instance null?
   /            \
 YES             NO
  │               │
  ▼               ▼
Create new    Return existing
instance      instance
  │               │
  ▼               │
Store it          │
  │               │
  └───────────────┘
          │
          ▼
   Return instance

Code Example (Java-style pseudocode)

class ConfigManager {
    private static ConfigManager instance = null;  // starts empty

    private ConfigManager() {
        // load config file here
    }

    public static ConfigManager getInstance() {
        if (instance == null) {
            instance = new ConfigManager();  // created only once
        }
        return instance;
    }

    public String getSetting(String key) { ... }
}

// Usage
ConfigManager config = ConfigManager.getInstance();
config.getSetting("theme");   // "dark"

// Call it again from a different class
ConfigManager same = ConfigManager.getInstance();
// same == config → true  (exact same object in memory)

Thread Safety Problem (Advanced Note)

In multi-threaded applications, two threads can both check instance == null at the same time and each create their own instance. This breaks the Singleton guarantee.

Thread 1: checks instance → null → starts creating...
Thread 2: checks instance → null → starts creating... ← problem!
Thread 1: finishes, stores instance A
Thread 2: finishes, stores instance B (overwrites A)

Result: Two instances exist. Singleton is broken.

The fix is to use double-checked locking or initialise the instance at class-load time, depending on the language.

Where Singleton Is Used in Real Projects

┌─────────────────────────────────────────────────────────┐
│  Common Singleton Use Cases                             │
├──────────────────────┬──────────────────────────────────┤
│ Logger               │ One log file, all messages go    │
│                      │ to the same place                │
├──────────────────────┼──────────────────────────────────┤
│ Configuration        │ One set of app settings loaded   │
│ Manager              │ once, shared everywhere          │
├──────────────────────┼──────────────────────────────────┤
│ Database Connection  │ One connection pool to avoid     │
│ Pool                 │ opening too many connections     │
├──────────────────────┼──────────────────────────────────┤
│ Cache Manager        │ One shared cache so all parts    │
│                      │ of the app see the same data     │
└──────────────────────┴──────────────────────────────────┘

Advantages and Disadvantages

ADVANTAGES                         DISADVANTAGES
─────────────────────────────────  ─────────────────────────────────
Saves memory (one object only)     Hard to unit-test in isolation
Consistent shared state            Global state can cause hidden bugs
Lazy initialisation possible       Violates Single Responsibility
Simple to access from anywhere     Can become a bottleneck under load

When to Use Singleton

  • Use it when exactly one object must coordinate actions across the system.
  • Use it for shared resources like loggers, config files, or thread pools.
  • Avoid it for objects that naturally need multiple instances, such as user profiles or shopping carts.

Key Takeaways

  • Singleton allows only one instance of a class to exist at any time.
  • The private constructor prevents external code from creating new instances.
  • getInstance() is the single entry point that creates or returns the shared object.
  • Thread safety needs extra care in concurrent applications.

Leave a Comment