SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignSingleton Pattern
Software DesignCreational Patterns

Singleton Pattern

Ensure a class has exactly one instance and provide a global access point — the most used (and misused) design pattern.

January 4, 20254 min read
design-patternsjavasingletoncreational

One-line Definition

Encapsulates object instantiation to guarantee exactly one instance of a class exists system-wide.


Problem

Suppose your application manages a database connection pool, a logger, or a global configuration file. If every component instantiates its own database pool, you'll quickly exhaust database connections. The Singleton Pattern ensures all components share the exact same instance, coordinating access to the shared resource and preventing resource leaks.


Real-world Analogy

A country has exactly one president at a time. You don't create a new president every time you need a government decision — you go to the one who's already in office. The title "President" is the global access point, and the constitution ensures there's exactly one.


Structure

  • Client
    • Accesses the singleton instance via its public static method.
  • Singleton
    • Holds a private static reference to its own instance.
    • Prevents external instantiation via a private constructor.
    • Exposes a getInstance() method.

Diagram

classDiagram
    class Client {
    }
    class Singleton {
        -static instance: Singleton
        -Singleton()
        +static getInstance(): Singleton
        +doLogic()
    }
    
    Client --> Singleton : calls getInstance()

Code Walkthrough

Notice that DatabaseConnection strictly controls its own instantiation. Main (the client) cannot use new and must use the shared getInstance() method.

public class DatabaseConnection {
    // Private constructor prevents external instantiation
    private DatabaseConnection() {
        System.out.println("Connecting to database...");
    }

    // Bill Pugh approach: static inner class for thread-safe lazy init
    private static class Holder {
        private static final DatabaseConnection INSTANCE = new DatabaseConnection();
    }

    public static DatabaseConnection getInstance() {
        return Holder.INSTANCE;
    }

    public void query(String sql) {
        System.out.println("Executing: " + sql);
    }
}

class Main {
    public static void main(String[] args) {
        DatabaseConnection db1 = DatabaseConnection.getInstance();
        DatabaseConnection db2 = DatabaseConnection.getInstance();

        db1.query("SELECT * FROM users");

        System.out.println("Same instance? " + (db1 == db2)); // true
    }
}

Bad vs Good

Bad Approach

Problems

  • The constructor is public, allowing multiple instances.
  • Simple lazy initialization without synchronization creates race conditions in multi-threaded environments.
public class Logger {
    private static Logger instance;

    public Logger() { // public constructor!
    }

    public static Logger getInstance() {
        if (instance == null) {       // Race condition!
            instance = new Logger();
        }
        return instance;
    }
}

Better Approach

Improvements

  • Thread-safe without the overhead of synchronized on every call.
  • Private constructor completely prevents external instantiation.
public class Logger {
    private Logger() {}

    private static class Holder {
        private static final Logger INSTANCE = new Logger();
    }

    public static Logger getInstance() {
        return Holder.INSTANCE;
    }
}

(Note: In modern Java, enum is often the safest way to implement Singletons, as it handles serialization and reflection attacks automatically).


Pros vs Cons

ProsCons
Guarantees a single instance across the applicationHard to unit test because of tight coupling to the global state
Provides a global access point to a shared resourceOften misused as a glorified global variable
Lazy initialization saves resources if unusedViolates the Single Responsibility Principle (manages own lifecycle)
Avoids resource exhaustion (e.g., DB connections)Makes dependency injection much harder
Thread-safe (when implemented correctly)Can cause bottlenecks in concurrent environments

When to Use

  • Exactly one instance is needed to coordinate system-wide actions.
  • You need a single pool of resources (threads, connections, caches).
  • Global state must be strictly controlled and protected from concurrent modification.
  • You require lazy initialization of a heavy shared resource.

When Not to Use

  • You just want a convenient way to access a variable globally (use DI instead).
  • The object holds state that shouldn't be shared across unrelated components.
  • You are working in an environment with robust Dependency Injection (like Spring), which handles object lifecycles for you.

Real-world Examples

  • java.lang.Runtime#getRuntime()
  • java.awt.Desktop#getDesktop()
  • java.lang.System#getSecurityManager()
  • Spring Beans (Default scope is singleton)

Key Takeaway

The Singleton Pattern ensures strict control over a single shared resource by encapsulating its instantiation and lifecycle. While incredibly useful for connection pools or caches, it introduces global state and tight coupling. Avoid it unless you genuinely need to restrict instantiation, and prefer Dependency Injection frameworks where possible.

PreviousIterator PatternNextPrototype Pattern