The Singleton: Why It Tempts Game Developers and Where It Hurts
The singleton solves a real problem in Unity, reaching one manager from anywhere, but it does so by turning every dependency into a hidden global, and that debt is paid later in tests, scene loads and refactors.

Almost every Unity project, somewhere in its first weeks, acquires a class called GameManager, and almost every GameManager, sooner or later, acquires a public static field named Instance. The gesture is so common that many developers learn it before they learn the word for it. One line of code, and the audio system, the score, the save file and the current day of the campaign are suddenly reachable from any script in the project, without dragging references through the Inspector or hunting for objects by name.
That line is the singleton pattern, one of the original entries in the catalogue of object oriented design patterns published by Gamma, Helm, Johnson and Vlissides in 1994. Its stated intent is modest: ensure a class has only one instance and provide a global point of access to it. The trouble lies almost entirely in the second half of that sentence, which sounds like a convenience and behaves, over the life of a project, much more like a slow and patient accumulation of debt.
None of this means the singleton is forbidden or foolish. It exists because it answers a genuine need, and a developer who has never felt that need has probably never shipped anything of size. The aim here is to describe the pattern honestly: how it is built in Unity, why it seduces, where it begins to cut, and what can stand in its place once the cuts become frequent enough to notice.
The shape of the pattern
In plain C# a singleton is usually a class with a private constructor and a static property that creates the one instance on first use. Unity complicates this, because a MonoBehaviour cannot be constructed with new; it must live on a GameObject. The familiar Unity version therefore declares a static Instance field, and in Awake the component checks whether Instance is already set. If it is empty, the component assigns itself; if another copy already holds the post, the newcomer destroys its own GameObject and quietly withdraws.
Most such managers also call DontDestroyOnLoad on their GameObject, so that the object survives when SceneManager.LoadScene replaces the current scene. Without it, the manager would be destroyed with the old scene and recreated, if at all, by whatever copy sits in the new one. With it, the manager becomes a kind of permanent resident, carried from the menu into the first level and onward, holding its fields intact while everything around it is torn down and rebuilt.
Some teams prefer a lazy variant, in which the Instance getter searches the scene with FindObjectOfType, or its newer replacement FindFirstObjectByType, and creates a fresh GameObject if nothing turns up. Others write a generic base class, so that any manager can inherit singleton behaviour with a single line. The details vary, but the essence is constant: a static reference, a guarantee of uniqueness enforced at runtime, and an open door through which any code in the project may walk.
Why it feels so good
The appeal is real and should be named plainly. Wiring references by hand is tedious work, and Unity's Inspector, generous as it is, only connects objects that exist in the same scene or the same prefab. A UI label that needs the player's gold, a sound effect that needs the mixer, a quest that needs to know the date: each of these asks the developer to solve a small routing problem, and the singleton solves all of them at once by declaring that the answer is simply everywhere.
There is also a matter of honesty in the domain itself. Many games really do have exactly one of certain things. There is one audio output, one save file in use, one clock that counts the hours until nightfall. When a settlement in a game like Crown & Ashes has a single sense of time, with torches lit at dusk and the dead approaching after dark, a single TimeOfDay object feels less like an architectural choice than like a plain description of the world.
Finally, the pattern is cheap at the moment it is adopted, and that is the most dangerous kind of cheap. On the day a developer writes AudioManager.Instance.Play, nothing bad happens. The game compiles, the sound plays, the task is closed. The costs arrive later and from directions that seem unrelated, so they are rarely traced back to the line that caused them, and the pattern keeps its excellent reputation long after it has begun to send its bills.
Global state and hidden dependencies
The central problem is that a singleton is global state wearing the clothes of an object. Any script may read it, and in most implementations any script may change it. When the player's health is wrong at the end of a fight, the question of who changed it has no small answer, because the honest answer is anyone. The search for the culprit becomes an archaeological dig through every file that mentions the manager, and in a mature project that list can run to hundreds.
Worse than the mutation is the concealment. A method whose signature reads void OpenGate() appears to depend on nothing, yet inside it may consult the economy, the save system and the audio manager, each through its static Instance. These are hidden dependencies: requirements that never appear in a constructor, a parameter list or a serialized field. A reader cannot see them without opening the body, and a refactor cannot respect what it does not know is there.
The concealment compounds over time in a way that is almost biological. Singletons begin to call one another, the InventoryManager reaching into the EconomyManager, which reaches into the SaveManager, which reaches back into the inventory to serialize it. Initialization order, which Unity does not guarantee between Awake methods on different objects unless Script Execution Order is configured, becomes a source of intermittent null references that appear on one machine and vanish on another, like a draught in an old house whose source no one can find.
Eventually the project reaches a state in which no class can be understood alone. Every component quietly assumes a whole constellation of managers already alive and correctly configured, and the only environment in which those assumptions hold is the full game, launched from the first scene. That is the moment when developers begin starting every test session from the main menu, not because they want to, but because nothing else works any longer.
Testing, reloads and duplicates
Automated testing is where the singleton's costs become impossible to ignore. A unit test wants to construct one class, give it controlled inputs and check its outputs. If that class reaches for EconomyManager.Instance, the test must either create a real economy, with all of its own dependencies, or find some way to replace the static reference with a fake. Neither is pleasant, and the second usually requires adding setters to the singleton, which weakens the very guarantee it was built to provide.
Static fields also outlive the things they point to in surprising ways. When Enter Play Mode Options are configured to skip domain reload, which many teams do to shorten iteration time, static values persist between play sessions in the Editor. A singleton that was never cleared will still hold a reference to an object destroyed when the last session ended, and the next session begins with a ghost. The usual remedy, a static reset method marked with RuntimeInitializeOnLoadMethod, is easy to forget.
Scene reloads produce their own classic failure. Suppose the GameManager lives in the first gameplay scene and calls DontDestroyOnLoad. When the player dies and that scene is loaded again, the scene's copy of the GameManager wakes up beside the surviving original. If the Awake check is missing or subtly wrong, there are now two managers, both listening to events, both awarding points, and the bug report says only that the score sometimes doubles after a restart.
The duplicate guard handles the common case, but it has a cost of its own: the destroyed copy may have carried serialized settings that differed from the survivor's, and those differences vanish without a word. Teams often respond by moving all persistent managers into a dedicated bootstrap scene that loads once and never reloads, which is a sensible arrangement. It is also, worth observing, the first step toward an explicit composition root, the very thing the singleton was meant to avoid.
Alternatives that keep the convenience
One gentle alternative in Unity is the ScriptableObject used as a shared service or data container. A ScriptableObject asset, such as a GameClock or a PlayerWallet, lives in the project rather than in a scene, so it survives scene loads without DontDestroyOnLoad. Components receive it through an ordinary serialized field, dragged in through the Inspector. The dependency is now visible in the component's fields, and a test or a debug scene can hand in a different asset with different values.
ScriptableObjects carry a caveat that catches nearly everyone once. In the Editor, changes made to an asset during play mode persist after play mode ends, because the asset itself was modified. Runtime state should therefore be reset in OnEnable or kept in non serialized fields, or the developer will find that the player's gold from yesterday's test is still sitting in the wallet this morning, a small haunting that is entirely self inflicted.
The broader alternative is dependency injection, in which an object is handed what it needs instead of fetching it. Because Unity creates MonoBehaviour instances itself, constructor injection is not available for components; instead dependencies arrive through an initialization method or through fields filled by a framework. Libraries such as Extenject, the maintained fork of Zenject, and VContainer automate this, but the idea needs no library: one bootstrap object creates the services and passes them to the components that ask.
Living with a few singletons
Pragmatism allows a small number of singletons to remain, provided they are chosen deliberately. A logging service, a thin wrapper around the platform's achievements API, or a profiler hook are reasonable candidates: they are genuinely unique, mostly stateless, and rarely central to gameplay logic. The danger lies less in any individual singleton than in the habit of reaching for the pattern whenever wiring feels tedious, which is how a project ends up with forty of them.
A useful discipline is to keep access to a singleton at the edges of the code. A component may read AudioManager.Instance once in Start and store it in a private field, or better, receive it through a serialized reference, and then use that field everywhere else. The dependency is still global in origin, but it is now declared in one place, so replacing it later means changing one line rather than chasing calls across the whole file.
Seen from a distance, the singleton is a loan taken against the future clarity of a codebase. Small loans are useful, and many shipped games have carried several without harm. What matters is knowing that the interest is real, that it is paid in testing difficulty, opaque bugs and brittle scene transitions, and that a project which keeps borrowing will, some quiet evening near a deadline, discover how much it owes.

