Last Updated: February 21, 2026
In software development, we often require classes that can only have one object.
Example: thread pools, caches, loggers etc.
Creating more than one objects of these could lead to issues such as incorrect program behavior, overuse of resources, or inconsistent results.
This is where Singleton Design Pattern comes into play.
It is one of the simplest design patterns, yet challenging to implement correctly.
In this chapter, we will explore what Singleton pattern is, how it works, different ways you can implement it, real-world examples where it’s used and it’s pros and cons.
Singleton Pattern is a creational design pattern that guarantees a class has only one instance and provides a global point of access to it.
Two requirements define the pattern:
Think of a print spooler in an operating system. There is one spooler managing all print jobs. Applications do not create their own spoolers. They submit jobs to the one that exists. If each application ran its own spooler, print jobs would conflict, pages would interleave, and the printer would produce garbage.
The single spooler coordinates everything.
Singleton is useful in scenarios like:
To implement the singleton pattern, we must prevent external objects from creating instances of the singleton class. Only the singleton class should be permitted to create its own objects.
Additionally, we need to provide a method for external objects to access the singleton object.
instance field stores the one and only Singleton object.getInstance() (or similar) class-level method returns the shared instance and is accessible from anywhere.Global variables in languages that support them have similar accessibility but no initialization control. A Singleton can control when and how the instance is created, perform lazy initialization, enforce thread safety during construction, and validate that only one instance ever exists.
The Singleton workflow is straightforward:
A client calls Singleton.getInstance(). The method checks if an instance already exists.
If no instance exists, the method creates one using the private constructor and stores it in the static field.
The method returns the newly created instance.
Later calls to getInstance() find the instance already exists and return it immediately, skipping creation entirely.
The sequence diagram above shows two clients requesting the instance. The first triggers creation; the second returns the existing one. Both end up with references to the same object.
Singleton implementation varies across languages. The central challenge is thread safety: if two threads call getInstance() simultaneously when the instance has not been created yet, both might create separate instances.
We will start with the simplest (but broken) approach and progressively improve it. After the shared implementations, we cover language-specific idioms that are recommended for production use.
This approach creates the singleton instance only when it is needed, saving resources if the singleton is never used in the application.
getInstance() method checks if an instance already exists.This implementation is not thread-safe. If multiple threads call getInstance() simultaneously when instance is null, it's possible to create multiple instances.
This approach extends lazy initialization by ensuring the Singleton is safe to use in multi-threaded environments.
When multiple threads try to access the instance at the same time, synchronization (or locking) ensures that only one thread can create the object, while others wait.
This approach is correct but has a performance cost: every call to getInstance() acquires a lock, even after the instance has been created. Once the instance exists, there is no reason to synchronize. The next approach fixes this.
Double-checked locking reduces the performance overhead by only synchronizing during the first object creation. After the instance exists, threads skip the lock entirely.
Although this approach is more complex to implement, it can drastically reduce performance overhead, especially when the singleton is accessed frequently.
In eager initialization, the Singleton instance is created as soon as the class/module is loaded, before any thread can access it. That makes it inherently thread-safe without explicit locks, because initialization happens once during load/initialization.
This approach is suitable if your application always creates and uses the singleton instance, or the overhead of creating it is minimal.
While it is inherently thread-safe, it could potentially waste resources if the singleton instance is never used by the client application.
This approach uses a static inner class to defer instance creation until getInstance() is first called. The JVM guarantees that inner classes are not loaded until they are referenced, giving us lazy initialization without synchronization:
The JVM's class loading mechanism does the heavy lifting here. Holder is not loaded when Singleton is loaded. It is only loaded when getInstance() is called for the first time, which triggers the initialization of INSTANCE.
Class initialization in Java is guaranteed to be thread-safe by the JLS (Java Language Specification), so no explicit synchronization is needed.
The Bill Pugh Singleton implementation, while more complex than Eager Initialization provides a perfect balance of lazy initialization, thread safety, and performance, without the complexities of some other patterns like double-checked locking.
This is the simplest and safest approach in Java. Joshua Bloch recommends it in Effective Java as the best way to implement a Singleton:
The JVM provides four guarantees that no other approach offers:
Constructor.newInstance() throws an IllegalArgumentException.The only limitation is that enums cannot extend other classes (they implicitly extend java.lang.Enum), so if your Singleton needs a base class, you cannot use this approach.
Similar to eager initialization, but uses a static initializer block. The advantage is the ability to handle exceptions during instance creation:
Use this when the constructor might throw checked exceptions and you want to handle them gracefully during class loading rather than propagating them as unhandled errors.
It is thread safe but not lazy-loaded, which might be a drawback if the initialization is resource-intensive or time-consuming.
Lets say you are building an application where multiple components (HTTP handlers, database layer, background jobs) all need to cache expensive data like user profiles, configuration, and query results.
You want one shared cache so that any component's writes are immediately visible to all others, without duplicate maps, stale reads, or wasted memory.
All components access the single CacheManager instance, which manages one shared map, handles TTL expiry on reads, and synchronizes access internally.
put() is immediately visible to all othersIt's important to note that the Singleton pattern should be used judiciously, as it introduces global state and can make testing and maintenance more challenging.
Consider alternative approaches like dependency injection when possible to promote loose coupling and testability.