The Singleton Anti-Pattern
The most common disease in game codebases - and why the real problem is cultural, not syntactic.
The Most Popular Pattern in Game Development
Open any game development tutorial, any “getting started” guide, any sample project. You will find a GameManager. It will be a singleton. It will be the hub through which everything in the game communicates.
public class GameManager : MonoBehaviour {
public static GameManager Instance { get; private set; }
public PlayerStats Stats;
public InventorySystem Inventory;
public QuestManager Quests;
public AudioManager Audio;
public UIManager UI;
public SaveSystem Save;
public AnalyticsService Analytics;
public NetworkManager Network;
void Awake() {
if (Instance != null) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
This pattern is everywhere. It’s in tutorials. It’s in Unity Asset Store packages. It’s in published games. Developers reach for it instinctively because it solves an immediate problem: “How does system A talk to system B?”
The answer - GameManager.Instance.B.DoSomething() - is always available, always obvious, and always wrong for any project that will grow beyond a prototype.
The Real Problem Is Not the Static Instance
Most critiques of singletons focus on the static Instance property. They argue that global state is bad, that static access creates hidden dependencies, that it makes testing hard.
These are valid concerns, but they miss the deeper issue. The real damage of the Singleton pattern in games is cultural, not syntactic. It establishes a mindset:
“There is one central place that knows about everything. When I need something, I go there.”
This mindset produces specific, structural consequences:
1. God Objects
The singleton becomes the place where everything accumulates. Need a reference to the player? Add it to the GameManager. Need access to the save system? It’s on the GameManager. Need to trigger audio? GameManager.
Over time, the singleton grows into a god object - a class that has too many responsibilities and too many dependents. Every system in the game knows about the GameManager, and the GameManager knows about every system in the game.
The dependency graph becomes a star topology:

Every arrow in this diagram is a coupling. Changing the GameManager ripples to everything. Changing anything requires understanding the GameManager.
2. Invisible Dependencies
When a class accesses GameManager.Instance.Quests, the dependency on the quest system is hidden. It doesn’t appear in the constructor. It doesn’t appear in the interface. It’s buried inside a method body.
This means:
- You can’t tell what a class depends on by looking at its declaration
- You can’t substitute test doubles without modifying global state
- You can’t reason about a class without understanding everything it reaches through the singleton
3. No Architectural Direction
Singletons erase the concept of dependency direction. Any class can depend on any other class through the singleton hub. There’s no “inner” or “outer.” There are no layers. There’s just everything connected to everything through one point.
This is the direct opposite of the Architecture Model, which demands that dependencies point in one direction only.
4. Coupling Culture
The most insidious effect is cultural. Once a project has a singleton hub, it becomes the default solution for every communication problem. Developers stop asking “what is the right way for system A to talk to system B?” and start asking “where on the GameManager should I put this?”
The singleton doesn’t just create coupling - it normalizes coupling. It removes the friction that would otherwise force developers to think about dependencies, boundaries, and interfaces.
Why “Just Use DI” Doesn’t Fix It
A common response to singleton criticism is: “Replace it with dependency injection.” Swap GameManager.Instance for [Inject] IGameManager. Problem solved.
Problem not solved.
If you take a GameManager singleton and make it an injected interface, you still have:
- A god interface that exposes everything
- 40 classes that all depend on the same central hub
- No directional dependency rules
- No layer boundaries
You’ve changed the access mechanism from static Instance to [Inject], but the dependency graph is identical. This is a singleton in disguise - the same structural problem with different syntax.
(This is discussed in detail in Dependency Injection - The Singleton in Disguise)
What Singletons Are Trying to Solve
Singletons are a symptom, not a root cause. They emerge because developers face a real problem: systems need to communicate, and without architectural guidance, the singleton is the path of least resistance.
The problems singletons attempt to solve are legitimate:
- “The quest system needs to know about the player’s stats” This is a real dependency. It should be expressed through a small, focused interface injected into the quest system.
- “When the player levels up, the UI needs to update” This is a communication need. It should be handled through domain events, not through a shared reference.
- “The save system needs access to all game state” This is an integration concern. It should be handled by the persistence layer, which lives in the outermost ring and receives domain state through ports.
Every singleton usage corresponds to a legitimate need. The architecture model provides legitimate solutions for each of these needs - solutions that don’t require everything to know about everything.
How to Dismantle the Singleton
If your project already has a GameManager singleton (most do), here’s the path forward:
Step 1: Identify What It Actually Does
List every responsibility the singleton has. In most cases, it’s a grab bag: player data, level state, audio, UI, persistence, analytics, networking. Each of these is a separate concern.
Step 2: Extract Interfaces
For each responsibility, define a small interface that expresses only what consumers actually need:
IPlayerStatsinstead ofGameManager.StatsIAudioServiceinstead ofGameManager.AudioIQuestLoginstead ofGameManager.Quests
Step 3: Assign Layers
Determine where each interface belongs in the architecture:
IPlayerStats-> Core (domain state)IQuestLog-> Core (domain state)IAudioService-> Services layer (engine adapter)IPersistence-> Core port, implemented in Services
Step 4: Wire Through DI
Register each implementation with the DI container. Classes that need player stats receive IPlayerStats through injection - they never know about a GameManager.
Step 5: Use Events for Cross-Cutting Reactions
Replace direct calls like GameManager.Audio.Play("levelup") with domain events. The Core emits OnPlayerLeveledUp. An audio adapter in the Services layer subscribes and plays the appropriate sound. The Core has no knowledge of audio.
Step 6: Delete the GameManager
Once all responsibilities have been extracted and all references have been redirected, the singleton is empty. Delete it.
This is not a weekend project for a large codebase. It’s a gradual migration. But each step makes the code better, and each extracted responsibility reduces the coupling surface. For a broader strategy on how to introduce clean architecture into an existing codebase - including the organizational challenges - see Transition from Legacy.
The Deeper Lesson
The singleton pattern persists in game development because it offers instant gratification. Need access to something? GameManager.Instance. Done.
Clean architecture requires more upfront thought: define an interface, place it in the right layer, wire it through DI, communicate through events. It’s more work per interaction.
But the total cost of the singleton approach grows exponentially with project size, while the cost of the clean approach grows linearly. At some scale - and that scale comes sooner than most teams expect - the clean approach is not just better, it’s the only viable option.
The singleton is not a solution. It’s a deferred cost. And in game development, that cost comes due faster and harder than in almost any other domain.