Singleton is usually introduced as a creational design pattern, one of the three broad pattern categories described by the GoF alongside structural and behavioral patterns. On paper, it sounds straightforward: make sure a class has only one instance and provide a single access point to it. In practice, that promise often creates more problems than it prevents.
The central issue is the assumption built into the pattern itself. Declaring that there will only ever be one instance is a strong design decision, and often a premature one. Systems change, requirements shift, and code that once seemed simple can become rigid. What starts as a convenient shortcut can leave you with an architecture that is difficult to adapt because too much of it depends on that one shared object.
Another reason singleton draws criticism is the way it is commonly used as a disguised global. Developers who are cautious around global variables should be equally cautious here. A singleton may look more disciplined than a raw global variable, but in many codebases it serves the same purpose: a globally reachable object that any part of the application can depend on. That kind of hidden reach tends to blur boundaries in the code and makes behavior harder to reason about.
This also leads to confusion over time. Once a singleton becomes widely accessible, it can quietly accumulate responsibilities and dependencies. Different parts of the system begin to rely on it in ways that are not always obvious, and the result is code that feels interconnected in messy ways rather than cleanly separated.
Tight coupling is one of the biggest practical consequences. Because so many components can end up depending directly on the singleton, replacing it or isolating it during tests becomes awkward. In many cases, faking or mocking singleton-backed behavior is much harder than it should be. Testability suffers because the code is bound to a specific shared instance instead of depending on something that can be swapped more easily.
Singletons can also be a poor fit in multithreaded environments. With only one object available, your threading options are naturally constrained, and coordinating access to that instance can become a source of complexity. Instead of simplifying the design, the singleton can introduce contention and synchronization concerns.
Performance is another point raised against the pattern. While singleton is often chosen in the name of efficiency or control, it can reduce performance depending on how access and synchronization are implemented. The pattern does not automatically make a system leaner simply because it limits instantiation.
That said, treating singleton as universally wrong is too simplistic. There are cases where restricting a type to a single instance is justified. A useful rule of thumb is to ask whether having more than one instance would actually be dangerous. If multiple instances could create inconsistent behavior, conflicting ownership, or some other real risk, then singleton may be worth considering.
Even then, the decision should come with a concrete logistical reason for locking the object down to one instance. If that explanation is weak, the pattern is likely being used out of habit rather than necessity.
So the problem is not just that singleton exists as a creational pattern. The problem is how easily it invites inflexible design, global-style access, tight coupling, testing friction, and threading limitations. It can be appropriate in narrow situations, but outside those cases it often ends up behaving less like a helpful pattern and more like an antipattern.