Core Theory

The Singleton Anti-Pattern

The most common disease in game codebases - and why the real problem is cultural, not syntactic.

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:

Star topology - every system depends on the central GameManager

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:

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:

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:

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:

Step 3: Assign Layers

Determine where each interface belongs in the architecture:

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.