The singleton pattern is a creational design pattern that ensures a class has exactly one instance and provides a global point of access to that instance. In software architecture, it is used for objects that represent a single shared resource, such as a configuration registry, a logging service or a connection pool, where several copies would be wasteful or inconsistent.
The singleton is one of the 23 patterns described in the 1994 book *Design Patterns* by Gamma, Helm, Johnson and Vlissides. It is also one of the most criticized patterns in modern programming, because the global access it provides creates hidden dependencies and makes code harder to test.
How the singleton pattern works
A classic singleton combines three elements. The class makes its constructor private so that other code cannot create instances, stores the single instance in a static field, and exposes a static accessor method, often named getInstance(), that creates the instance on first use and returns the same object afterward.
- Private constructor: blocks
newcalls from outside the class. - Static instance field: holds the one object for the lifetime of the process or class loader.
- Static accessor: returns the instance and controls when it is created.
- Optional lazy initialization: delays creation until the first call to save startup time and memory.
The “one instance” guarantee has a precise scope. In Java it means one instance per class loader, in .NET one per application domain, and in a web application server one per process. A system with several processes or servers therefore has several singletons, one in each, which surprises developers who expect a truly global object.
Implementation variants and thread safety
The naive lazy singleton is not thread-safe: two threads can call the accessor at the same moment, both see an empty field and both create an instance. Production implementations use one of several well-known variants, compared in the table below.
| Variant | Thread-safe | Lazy | Notes |
|---|---|---|---|
| Eager initialization | Yes | No | Instance created when the class loads |
| Synchronized accessor | Yes | Yes | Simple, but locks on every call |
| Double-checked locking | Yes, with a volatile field | Yes | Error-prone without memory-model guarantees |
| Initialization-on-demand holder | Yes | Yes | Idiomatic in Java, uses class loading rules |
| Enum singleton | Yes | No | Java; resists reflection and serialization copies |
| Language-level object | Yes | Usually | Kotlin object, Scala object, Python modules |
Double-checked locking is the classic trap. Without the volatile keyword in Java or an equivalent memory barrier, another thread may see a reference to a partially constructed object. Many languages now offer safer built-in options, such as Lazy<T> in C# or a function-local static variable in C++11 and later, which the standard guarantees is initialized only once.
Singleton compared with related patterns
Most problems that a singleton solves can also be solved by other architectural techniques. The comparison below shows where each option fits.
| Approach | Number of instances | How code obtains it | Testability |
|---|---|---|---|
| Singleton pattern | One, enforced by the class | Global static accessor | Low |
| Static utility class | None, only static methods | Direct static calls | Low |
| Dependency injection with singleton scope | One, managed by a container | Constructor parameter | High |
| Factory pattern | Any, decided by the factory | Factory method call | Medium to high |
| Monostate | Many objects sharing static state | Normal constructor | Low |
The most important distinction is between enforcing one instance and using one instance. With dependency injection, a container creates a single object and passes it to every class that needs it. The application still uses one instance, but classes receive it explicitly, so a test can pass a fake object instead. This follows inversion of control and is the approach most modern frameworks recommend.
When a singleton is appropriate
A singleton is defensible when the object is genuinely unique by nature, stateless or nearly stateless, and expensive or unsafe to duplicate. It is rarely the right choice for business logic. The decision table below summarizes typical cases.
| Situation | Recommendation | Reason |
|---|---|---|
| Hardware interface with one device | Singleton acceptable | Physical resource is unique |
| Application-wide logger | Singleton or injected instance | Stateless and widely used |
| Configuration loaded at startup | Injected read-only object | Easier to vary in tests |
| Database connection pool | Container-managed single instance | Lifecycle control and shutdown |
| User session or request data | Avoid singleton | State differs per user or request |
In applications that already use a framework with a dependency injection container, such as Spring, ASP.NET Core or Angular, the usual answer is to register the service with singleton scope instead of writing a singleton class. The container then guarantees one instance, controls its creation and disposal, and still allows a different implementation in tests.
Criticism and common pitfalls
Critics often call the singleton an anti-pattern when it is used as a convenient global variable. The core objection is that it hides dependencies: a class that calls Config.getInstance() does not declare in its constructor that it needs configuration, which conflicts with the SOLID principles and with separation of concerns.
- Hidden coupling: many classes depend on the singleton without stating it, so changes ripple unpredictably.
- Test interference: state left in the singleton by one test leaks into the next and causes order-dependent failures.
- Mutable global state: concurrent writes require locking and can become a performance bottleneck.
- Lifecycle problems: shutdown order and cleanup are hard to control for objects created on first access.
- Duplicate instances can still appear through reflection, serialization, cloning or multiple class loaders.
A practical rule is to keep singletons immutable after creation and to access them through an interface, so that a test or a future redesign can replace them without rewriting every caller.
A non-technical view
The singleton pattern is a rule in a program that says there may be only one of this thing. Think of a building with a single main electricity meter. Every apartment uses the same meter, and nobody is allowed to install a second one.
This is useful for things that really should exist once, such as the settings of an application. The problem is that everyone depends on it without saying so. When something changes in that single shared object, it is hard to know which parts of the program will notice. That is why careful developers use it sparingly.
A concrete example
A team builds a desktop application that talks to a USB barcode scanner. The operating system allows only one open connection to the device, and opening it twice causes errors. The team creates a ScannerService class with a private constructor and a thread-safe lazy accessor, so every screen that needs scanning shares the same connection.
Later the team adds automated tests. Because screens call ScannerService.getInstance() directly, tests try to open a real device and fail on build servers. The team refactors: the singleton now implements a Scanner interface, and screens receive it through their constructors. Production still uses one real scanner instance, while tests pass a simulated scanner. The change keeps the hardware guarantee and makes the code testable.