Difficulty Level: Beginner
The singleton pattern appears in countless Unity tutorials and Stack Overflow answers. It promises convenient global access to game managers, audio controllers, and player data. However, this convenience comes with significant hidden costs.
While singletons solve immediate problems, they create architectural issues that surface later in development—when adding features, debugging crashes, or managing scene transitions. These problems often appear at the worst possible times.
This guide explains why singletons cause problems in Unity and presents better alternatives that are more maintainable and testable.
What Is the Singleton Pattern?
Before we dive into the problems, let’s make sure we’re on the same page about what a singleton actually is.
The singleton pattern ensures that only one instance of a class exists throughout the entire application, and provides a global access point to that instance.
Here’s what a typical Unity singleton looks like:
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
public int playerScore = 0;
public bool gameIsPaused = false;
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
public void AddScore(int points)
{
playerScore += points;
}
}
And then you’d use it like this from anywhere in your code:
// From any script, anywhere
GameManager.Instance.AddScore(100);
This appears convenient but creates significant architectural problems.
The Problems with Singletons in Unity
1. Testing Becomes Extremely Difficult
Let’s say you want to test a script that uses your GameManager singleton:
public class Enemy : MonoBehaviour
{
public void Die()
{
// This line makes testing really hard!
GameManager.Instance.AddScore(50);
Destroy(gameObject);
}
}
The problem: How do you test the Enemy.Die()
method without setting up the entire GameManager singleton? You can’t! Your test either fails because GameManager.Instance
is null, or you have to create a whole complex test setup just to test one simple method.
2. Hidden Dependencies
When you use singletons, it’s not obvious what a script depends on just by looking at it:
public class PlayerController : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// Hidden dependency! Not visible in the inspector
AudioManager.Instance.PlaySound("jump");
GameManager.Instance.AddScore(10);
UIManager.Instance.UpdateScoreDisplay();
}
}
}
The problem: Looking at this script in the inspector, you have no idea it depends on AudioManager, GameManager, and UIManager. This makes debugging and understanding your code much harder.
3. Scene Loading Issues
Unity’s scene management and singletons don’t play well together:
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject); // What happens to references to this instance?
return;
}
Instance = this;
DontDestroyOnLoad(gameObject); // This can cause problems too!
}
The problems:
- What if other scripts already have references to the old instance when it gets destroyed?
DontDestroyOnLoad
objects can accumulate and cause memory leaks- Scene reloading in the editor can leave you with stale singleton instances
4. Initialization Order Problems
Consider this scenario:
public class AudioManager : MonoBehaviour
{
void Start()
{
// This might fail if GameManager isn't initialized yet!
bool soundEnabled = GameManager.Instance.soundEnabled;
SetupAudio(soundEnabled);
}
}
The problem: Unity’s script execution order isn’t guaranteed. AudioManager might try to access GameManager before GameManager’s Awake()
method runs, causing a null reference exception.
What to Use Instead: Better Unity Patterns
Option 1: ScriptableObjects
ScriptableObjects are often the best alternative to singletons for shared data:
[CreateAssetMenu(fileName = "GameData", menuName = "Game/GameData")]
public class GameData : ScriptableObject
{
[Header("Player Stats")]
public int playerScore;
public int playerLives = 3;
[Header("Game Settings")]
public bool soundEnabled = true;
public float gameSpeed = 1.0f;
public void AddScore(int points)
{
playerScore += points;
}
public void ResetGame()
{
playerScore = 0;
playerLives = 3;
}
}
Now your scripts can reference this data directly:
public class PlayerController : MonoBehaviour
{
[SerializeField] private GameData gameData; // Visible dependency!
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
gameData.AddScore(10);
}
}
}
Why this is better:
- ✅ Dependencies are visible in the inspector
- ✅ Easy to test (just create a test GameData asset)
- ✅ No initialization order problems
- ✅ Data persists between scene loads automatically
- ✅ You can have multiple configurations (test data, level-specific data, etc.)
Option 2: Simple References
Sometimes the simplest solution is the best:
public class GameManager : MonoBehaviour
{
[Header("Game State")]
public int playerScore = 0;
public bool gameIsPaused = false;
public void AddScore(int points)
{
playerScore += points;
}
}
And then pass references where needed:
public class Enemy : MonoBehaviour
{
[SerializeField] private GameManager gameManager; // Clear dependency!
public void Die()
{
if (gameManager != null)
{
gameManager.AddScore(50);
}
Destroy(gameObject);
}
}
Why this works well:
- ✅ Super clear what depends on what
- ✅ Easy to test by passing in a mock GameManager
- ✅ No magic static references
- ✅ Works perfectly with Unity’s serialization system
Option 3: Simple Dependency Injection
If you want to get a bit more sophisticated, you can create a simple service locator:
public class Services : MonoBehaviour
{
public static Services Instance { get; private set; }
[SerializeField] private GameManager gameManager;
[SerializeField] private AudioManager audioManager;
[SerializeField] private UIManager uiManager;
void Awake()
{
Instance = this;
}
public GameManager GetGameManager() => gameManager;
public AudioManager GetAudioManager() => audioManager;
public UIManager GetUIManager() => uiManager;
}
Then inject dependencies at start:
public class PlayerController : MonoBehaviour
{
private GameManager gameManager;
private AudioManager audioManager;
void Start()
{
// Get dependencies at startup
gameManager = Services.Instance.GetGameManager();
audioManager = Services.Instance.GetAudioManager();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && gameManager != null)
{
gameManager.AddScore(10);
audioManager?.PlaySound("jump");
}
}
}
When Singletons Are Acceptable
There are limited cases where singletons can be appropriate in Unity:
Input Management
public class InputManager : MonoBehaviour
{
public static InputManager Instance { get; private set; }
void Awake()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
This is somewhat acceptable because:
- Input is truly global
- It rarely needs to be mocked for testing
- It doesn’t hold game state
But even then, using Unity’s new Input System is usually better!
Migration Strategy: From Singletons to Better Patterns
If you have existing singleton code, here’s how to refactor it safely:
Step 1: Identify Your Singletons
List all the singleton classes in your project and categorize them:
- Data holders (GameManager, PlayerData) → Convert to ScriptableObjects
- Service providers (AudioManager, UIManager) → Use direct references
- Utilities (MathHelper, Extensions) → Make them static classes instead
Step 2: Convert One at a Time
Don’t try to refactor everything at once! Pick the singleton that causes you the most problems and convert it first.
Step 3: Update References Gradually
You can even have a transition period:
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; } // Keep for now
[SerializeField] private GameData gameData; // New approach
void Awake()
{
Instance = this; // Remove this eventually
}
// Old method - mark as deprecated
[System.Obsolete("Use gameData.AddScore() instead")]
public void AddScore(int points)
{
gameData.AddScore(points);
}
}
Common Questions and Concerns
Q: “But passing references everywhere is so much work!” A: It seems like more work at first, but it saves you huge amounts of debugging time later. Plus, Unity’s serialization system makes it pretty easy.
Q: “What about performance? Isn’t static access faster?” A: The performance difference is negligible. Clean, maintainable code is worth the tiny performance cost (which you probably won’t even measure).
Q: “I saw this pattern in a Unity tutorial…” A: Unfortunately, many Unity tutorials still teach singletons because they seem simpler for beginners. But they’re teaching bad habits that will bite you later.
Next Steps
- Audit your current project: How many singletons do you have?
- Try ScriptableObjects: Create a simple data container for your next feature
- Practice dependency injection: Start passing references instead of accessing static instances
- Test your code: You’ll be amazed how much easier testing becomes!
Frequently Asked Questions
Why is the Singleton pattern problematic in Unity?
Singleton pattern causes issues in Unity because: 1) It creates hidden dependencies making code hard to test, 2) Scene reloading can break singleton instances, 3) It violates Unity’s component-based architecture, 4) Makes code tightly coupled, 5) Can cause race conditions during initialization, and 6) Difficult to manage lifecycle with Unity’s scene system.
What are better alternatives to Singleton in Unity?
Better alternatives include: 1) ScriptableObject architecture for shared data, 2) Dependency injection for managing dependencies, 3) Event systems for decoupled communication, 4) Service locator pattern for global access, 5) Static classes for pure utility functions, and 6) Unity’s built-in systems like Input System’s action maps.
When is Singleton pattern acceptable in Unity?
Singleton can be acceptable for: 1) True single-instance managers (AudioManager, SaveManager), 2) Services that must persist across scenes, 3) When refactoring legacy code gradually. However, always consider ScriptableObjects or dependency injection first. If using Singleton, implement proper lazy initialization and handle scene transitions.
Conclusion
Singletons in Unity create more problems than they solve. While they provide convenient global access, they introduce hidden dependencies, testing difficulties, initialization order issues, and scene management complications.
Better alternatives include ScriptableObjects for shared data, direct references for component communication, and simple dependency injection for more complex scenarios. These patterns create code that is more maintainable, testable, and easier to understand.
When refactoring existing singleton-heavy codebases, migrate incrementally. Start with ScriptableObjects for game data and settings, then gradually replace other singletons with appropriate patterns.
Struggling with Unity architecture decisions? Contact us for a consultation - we’ve helped dozens of developers untangle their singleton-heavy codebases and build more maintainable Unity projects.

About Angry Shark Studio
Angry Shark Studio is a professional Unity AR/VR development studio specializing in mobile multiplatform applications and AI solutions. Our team includes Unity Certified Expert Programmers with extensive experience in AR/VR development.
Related Articles
More Articles
Explore more insights on Unity AR/VR development, mobile apps, and emerging technologies.
View All Articles