The Singleton Pattern in Unity: A Safe Implementation and the Pitfalls You Need to Know

Created: 2025-12-10Last updated: 2026-07-13

How to implement the Singleton pattern correctly in Unity and what to watch out for. Covers building a reusable MonoSingleton base class, the pitfalls of execution order and duplicate instances, and guidelines for avoiding overuse.

Tracking the score, playing BGM, managing game state—sooner or later, every game project needs "a single manager that can be reached from anywhere." Hunting it down with Find-style calls every time is slow and tedious, and manual Inspector references don't survive scene changes. The classic answer is the Singleton pattern.

But once Unity's lifecycle gets involved, Singletons come with their own set of traps, breeding bugs like "it's null sometimes" or "I switched scenes and now there are two of them." This article walks through a safe implementation using a reusable MonoSingleton<T> base class, the three big pitfalls, and the criteria for knowing when not to use a Singleton.

Illustration of the Singleton concept: many objects all accessing a single crowned manager

What You'll Learn

  • The problem Singletons solve and where they belong in game development
  • A reusable MonoSingleton<T> base class implementation (copy-paste ready)
  • Three pitfalls (duplicate instances, execution order, stale references) and how to fix them
  • Guidelines for avoiding overuse, and alternatives to Singletons

Sponsored

Where Do Singletons Belong?

Let's start with a picture of where Singletons fit. They are a good match for roles that are "exactly one per game" and "called from all over the place."

Real-world Singleton use cases: sound management, score management, and game flow management—three managers that exist exactly once per game
  • Sound manager: The single entry point for SFX and BGM playback. Enemies, UI, items—everyone asks it to play sounds
  • Score / save data management: A home for values that need to survive across scenes
  • Game flow management: The command center that owns states like title, playing, and paused (covered in depth in the GameManager pattern article)

Conversely, things that exist in multiples—enemies, bullets—or that only appear in a specific scene should never be Singletons. We'll look at what goes wrong when that line gets blurred later in the article.

The Basic Concept of the Singleton Pattern

The Singleton pattern guarantees that a class has exactly one instance across the entire application, and provides a global access point to that instance.

Singleton concept diagram: the unique instance acts as a front desk, reachable via Instance from anywhere—player, enemies, UI, and more

In a plain C# class, achieving this requires three main ingredients:

  1. A static instance variable: A static field that holds the class instance itself.
  2. A private constructor: Prevents external code from creating instances via new.
  3. A static access property: A static property that returns the unique instance.

Implementing a MonoBehaviour Singleton in Unity

When you implement the Singleton pattern in Unity, most manager classes need to inherit from MonoBehaviour—that's what gives you Unity's lifecycle methods like Awake and Update, plus Inspector configuration.

Here we'll build a reusable base class using generics that can turn any class into a Singleton. This approach is widely considered one of the best practices for Singleton implementation in Unity.

A Reusable MonoSingleton<T> Class

Save the following code as MonoSingleton.cs.

using UnityEngine;

// Constraint: T must be a class that inherits from MonoBehaviour
public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
    // Static variable holding the unique instance
    private static T instance;

    // Static property for external access
    public static T Instance
    {
        get
        {
            // If the instance doesn't exist yet
            if (instance == null)
            {
                // Search the scene for an object of type T
                instance = FindFirstObjectByType<T>();

                // If still not found, create a new GameObject and attach the component
                if (instance == null)
                {
                    GameObject singletonObject = new GameObject();
                    instance = singletonObject.AddComponent<T>();
                    singletonObject.name = typeof(T).ToString() + " (Singleton)";
                }
            }
            return instance;
        }
    }

    // Called right after the instance is created
    protected virtual void Awake()
    {
        // If an instance already exists (duplicate prevention)
        if (instance != null && instance != this)
        {
            // Destroy self to prevent duplicates
            Debug.LogWarning($"[Singleton] An instance already exists; destroying {typeof(T).Name}.");
            Destroy(gameObject);
            return;
        }

        // Register self as the unique instance
        instance = (T)this;

        // Persist the object across scene loads
        // Note: only applied at runtime, due to editor behavior caveats
        if (Application.isPlaying)
        {
            DontDestroyOnLoad(gameObject);
        }

        // Hand off initialization to the subclass
        OnInitialize();
    }

    // Clear the static reference on destruction
    protected virtual void OnDestroy()
    {
        if (instance == this)
        {
            instance = null;
        }
    }

    // Method for subclasses to override with their initialization logic
    protected virtual void OnInitialize() { }
}

Note: The scene search uses FindFirstObjectByType<T>(). The FindObjectOfType you'll see in older tutorials is a legacy API that was deprecated in Unity 6 (2023.1 and later). See the old-to-new API migration guide for the full mapping.

Usage (Example: Sound Manager)

Next, create an actual manager class by inheriting from this base class.

using UnityEngine;

// Inherit from MonoSingleton<SoundManager>
public class SoundManager : MonoSingleton<SoundManager>
{
    // Public methods for external access
    public void PlayBGM(AudioClip clip)
    {
        Debug.Log($"Playing BGM: {clip.name}");
        // Actual BGM playback logic...
    }

    public void PlaySE(AudioClip clip)
    {
        Debug.Log($"Playing sound effect: {clip.name}");
        // Actual SFX playback logic...
    }

    // Override the initialization hook
    protected override void OnInitialize()
    {
        // One-time setup such as loading sound settings goes here
        Debug.Log("SoundManager initialization complete.");
    }
}

Accessing the Singleton

Now you can reach it from anywhere in the game, without worrying about which scene you're in.

// From some other script
public class PlayerController : MonoBehaviour
{
    public AudioClip jumpSound;

    void Jump()
    {
        // Access the unique instance and call its method
        SoundManager.Instance.PlaySE(jumpSound);
    }
}

Key point: If no instance exists, the Instance property of MonoSingleton<T> automatically searches the scene, and if nothing is found, creates a new GameObject and attaches the component. Even if you forget to place the manager in a scene manually, nothing breaks—which makes this design considerably safer.

Sponsored

Three Common Pitfalls for Beginners and How to Fix Them

Singletons are convenient, but Unity's lifecycle introduces a few problems that beginners run into again and again.

The three Singleton pitfalls: duplicate instances on scene transitions, null access due to execution order, and stale static references after destruction

1. Duplicate Instances on Scene Transitions

The most common problem: a Singleton class using DontDestroyOnLoad gets duplicated when you move between scenes.

  • How it goes wrong: SoundManager is created in Scene A and persisted with DontDestroyOnLoad. When Scene B loads, if Scene B also contains a SoundManager prefab, a second instance is created.
  • The fix: As in the MonoSingleton<T> implementation above, check whether instance already exists inside Awake, and if it does, immediately destroy the newcomer with Destroy(gameObject);. This "gatekeeper check" mechanism is illustrated in detail in the DontDestroyOnLoad article.

2. Reference Order Problems (Script Execution Order)

In Unity, the order in which scripts run (when Awake and Start are called) is essentially undefined. If another script's Awake tries to access the Singleton's Instance, the Singleton's own Awake may not have run yet, leaving instance as null. That flaky "it worked yesterday, today it throws a null error" bug is almost always this.

  • The fixes:
    1. Use the create-on-demand logic inside the Instance property (as in the code above), which guarantees the instance exists whenever it's accessed.
    2. Use Script Execution Order settings (Edit -> Project Settings -> Script Execution Order) to make your Singleton classes run earlier than other scripts. This guarantees initialization finishes before anyone else touches them.

3. Forgetting to Clear the Static Reference in OnDestroy

Singleton objects can get destroyed during scene transitions or when the game shuts down. If you forget to clear the static instance variable at that point, a reference to an already-destroyed object lingers, potentially triggering unexpected errors (MissingReferenceException, etc.) on the next access.

  • The fix: Implement OnDestroy in the MonoSingleton<T> class and clear the static reference with instance = null; when the object is destroyed.
    // Clear the static reference on destruction
    protected virtual void OnDestroy()
    {
        if (instance == this)
        {
            instance = null;
        }
    }
Sponsored

Caution: Overuse Breeds Tight Coupling

The Singleton pattern is powerful, but its greatest strength—"accessible from anywhere"—is also its greatest weakness.

Singleton overuse diagram: a handful of managers keeps things clean, but making everything a Singleton tangles every class into mutual dependencies
  1. Tight coupling (strong dependencies): Because a Singleton provides a global access point, any class can reach it with zero effort. As a result, more and more classes come to depend on that specific Singleton, driving up coupling across your codebase. Tight coupling raises the risk that changing one piece of code breaks something in a completely unexpected place.

  2. Hard to test: In unit testing, a Singleton carries global state, so state leaks between tests and keeping tests independent becomes difficult. Singletons are also hard to swap out for mocks (fake objects), which limits testing flexibility.

Note (how large teams differ): In large-scale development, a technique called DI (Dependency Injection) is often adopted as a Singleton alternative precisely to avoid these downsides. For solo developers and small teams, though, the simplicity and convenience of Singletons often win out—so the key is to keep their scope limited. A good rule of thumb: only roles that truly exist once per game, such as sound, game flow, and save management.

Hands-On: Sharing One BGM Service Across Scenes

To finish, let's put these pieces into "one whole game." Three scenes — Title → Game → Results — with exactly one SoundManager as the gateway for BGM and sound effects. Even with ten enemies on screen, the enemies themselves are never Singletons. The goal here is to experience the boundary itself, not just the pattern.

Drawing the Singleton boundary: arrows from the player, UI, and enemies converge on the single SoundManager, while enemy HP — which exists in multiples — is never a Singleton

Setup steps:

  1. Place SoundManager in the title scene. Auto-creation inside Instance means it works even if you forget, but a manager that carries Inspector settings like AudioClips should have its "real" copy placed in the startup scene
  2. The player's jump, the UI's confirm sound, and enemy hit sounds all play through SoundManager.Instance.PlaySE(...) from anywhere
  3. Cycle the scenes three times and confirm the "DontDestroyOnLoad" section of the Hierarchy holds exactly one SoundManager

Then draw the line within the same game — which roles get to be Singletons and which don't:

RoleSingleton?Why
SoundManagerYesOne per game, spans scenes, called by everyone
Enemy Health / overhead HP barsNoMany exist. "Per-object news" belongs to instance events
Pause menu UINoLives in one scene. A plain Inspector reference is enough

The "Where Did That Sound Come From?" Symptom

When callers everywhere read SoundManager.Instance.PlaySE(...), the pain of tight coupling shows up as a concrete symptom. "The same sound effect plays twice for some reason" — so, who triggered it? Instance is a public button anyone can press, so the caller is anonymous, and your investigation devolves into a project-wide text search.

Two practical countermeasures during development: put a single Debug.Log(clip.name) inside PlaySE, and clicking the log in the Console shows the caller in the stack trace. The other is to stop multiplying the places that trigger sounds — concentrate firing points, like "the hit sound is played by exactly one subscriber of the enemy's Health event," and your dependencies stay within traceable range.

Break It on Purpose, Then Fix It

  1. Duplicate it by placing one per scene: Put a SoundManager in the game scene too and cycle scenes — the gatekeeper in Awake destroys the second copy and logs a warning (working as intended). Comment out the gatekeeper's Destroy and the two coexist, and the BGM starts playing doubled
  2. Access it before initialization: Try calling PlayBGM from another script's Awake. Thanks to create-on-demand in Instance you won't get null, but a playback request can still arrive before OnInitialize (settings loading) has run. Lock the order down with "place it in the startup scene + put the Singleton first in Script Execution Order"
  3. Recreate it during shutdown (the zombie Singleton): At the moment you stop Play mode, touching Instance from another object's OnDestroy can make the getter spawn a brand-new GameObject even though the real one is already destroyed. The telltale sign: "I stopped Play mode, but a SoundManager (Singleton) appeared in the Hierarchy"

Note (the quit flag): The standard fix for #3 is a quit flag: set isQuitting = true in OnApplicationQuit, and bail out with if (isQuitting) return null; at the top of the Instance getter. We've kept it out of the main code to keep things simple — add this pair the moment you see the symptom.

What deserves a final look isn't the game — it's your code. Search the project for Instance: if everything that comes up is a "one per game" role like SoundManager, your boundary is holding. If it has crept into enemy or HP-bar code, hand that job back to events or Inspector references.

Bonus: Good to Know for Later

Once you're comfortable with Singletons, these topics are the natural next steps.

  • If you only need notifications, you may not need a Singleton at all: When the goal is just "I want to call a method on the manager," the connection can often be replaced with events and delegates or event channels, sidestepping tight coupling. Get in the habit of asking whether you need to access something or merely notify it.
  • The persistence mechanism itself: How DontDestroyOnLoad works and how duplicates arise is covered independently in the DontDestroyOnLoad article. It's the perfect background for understanding the MonoSingleton<T> in this article.
  • The flagship use case for Singletons: The GameManager, which owns game-wide state, is the most classic application of the Singleton pattern. The GameManager pattern article gives you the big-picture design.

Summary

We've covered how to implement the Singleton pattern correctly in Unity and what to watch out for. Used appropriately, this pattern keeps your game's management structure simple.

  • The Singleton pattern is a design pattern that guarantees a single instance and provides global access to it. Reserve it for managers that are "one per game, called from everywhere."
  • In Unity, the best practice is a generic base class (MonoSingleton<T>) that inherits from MonoBehaviour, giving you a safe, reusable Singleton implementation.
  • Performing a duplicate-instance check and destroy inside Awake prevents bugs on scene transitions.
  • Nulls caused by execution order are handled by create-on-demand in the Instance property plus Script Execution Order; stale references after destruction are handled by clearing in OnDestroy.
  • Singleton overuse leads to tight coupling. Replace "I just want to notify" connections with events, and limit Singletons to global manager roles.

Put this knowledge to work and make your Unity projects more efficient and more robust.