Unity's OnEnable() and OnDisable() methods look simple, but they can cause some surprisingly subtle bugs.
They are commonly used to subscribe and unsubscribe from events, start or stop listeners, refresh UI, and manage temporary behaviour. The catch is that these methods are tied to a component's enabled state, not simply to its creation or destruction.
Understanding exactly when they run can save you from duplicate event calls, memory leaks, unexpected behaviour, and null-reference exceptions.
Let's look at five common mistakes and how to avoid them.
1. Forgetting to Unsubscribe from Events
One of the most common patterns is subscribing to an event in OnEnable():
private void OnEnable()
{
GameManager.OnGameOver += HandleGameOver;
}
But if you subscribe without unsubscribing, the event publisher can continue holding a reference to your component.
The matching cleanup should happen in OnDisable():
private void OnEnable()
{
GameManager.OnGameOver += HandleGameOver;
}
private void OnDisable()
{
GameManager.OnGameOver -= HandleGameOver;
}
private void HandleGameOver()
{
Debug.Log("Game Over!");
}
Why does this matter?
Imagine a UI panel that can be enabled and disabled multiple times. If every OnEnable() adds another subscription, the same callback may eventually be invoked multiple times.
Even worse, if the publisher lives longer than the subscriber, failing to unsubscribe can keep references around longer than expected.
A useful rule
If you subscribe in OnEnable(), the safest default is:
Unsubscribe from the same event in
OnDisable().
This keeps the subscription tied to the component's active state.
2. Assuming OnEnable() Runs Only Once
OnEnable() does not mean "run once when this object is created."
It runs whenever the component becomes enabled and active.
For example:
private void OnEnable()
{
Debug.Log("Enabled!");
}
If you do this:
gameObject.SetActive(false);
gameObject.SetActive(true);
gameObject.SetActive(false);
gameObject.SetActive(true);
OnEnable() can run each time the object becomes active again.
The same principle applies when changing a component's enabled property:
myComponent.enabled = false;
myComponent.enabled = true;
This can trigger OnDisable() and OnEnable().
Why is this important?
Code like this can become problematic:
private void OnEnable()
{
LoadData();
CreateSomething();
StartListening();
}
If you expect all of this to happen only once, disabling and re-enabling the component may repeat those operations.
For one-time initialization, consider using Awake() or Start() when appropriate.
For example:
private void Awake()
{
Initialize();
}
private void OnEnable()
{
StartListening();
}
private void OnDisable()
{
StopListening();
}
The important distinction is:
-
Awake()→ initialization -
Start()→ initialization that can wait until the first frame -
OnEnable()→ happens whenever the component becomes enabled and active -
OnDisable()→ happens whenever the component becomes disabled or inactive
3. Confusing Disabled Components with Destroyed Objects
Disabling an object does not destroy it.
For example:
gameObject.SetActive(false);
The GameObject still exists. Its components still exist. Its fields still contain their values.
The component simply isn't active.
That means this:
private void OnDisable()
{
Debug.Log("Object disabled");
}
does not mean:
"This object is being destroyed."
It means:
"This component is no longer enabled and active."
An object can later become active again:
gameObject.SetActive(true);
and OnEnable() will run again.
Why does this distinction matter?
Suppose you clear important state in OnDisable():
private void OnDisable()
{
currentTarget = null;
}
You might assume the object is going away permanently.
But if another system re-enables it, the component now has to deal with that cleared state.
For cleanup that should happen specifically when an object is destroyed, OnDestroy() may be more appropriate.
private void OnDestroy()
{
// Final cleanup before the object is destroyed.
}
The lifecycle methods serve different purposes. Don't treat OnDisable() as a synonym for OnDestroy().
4. Unexpected Repeated Subscriptions
This mistake is closely related to the first one, but it deserves separate attention.
Consider:
private void OnEnable()
{
EventManager.OnScoreChanged += UpdateScore;
}
What happens if you forget the corresponding unsubscribe?
Every enable adds another subscription:
Enable → Subscribe
Disable
Enable → Subscribe again
Disable
Enable → Subscribe again
Eventually, one event can produce multiple calls:
OnScoreChanged
↓
UpdateScore()
UpdateScore()
UpdateScore()
This can lead to confusing bugs such as:
- UI updating multiple times
- Audio playing repeatedly
- A method being executed several times
- Performance gradually getting worse
- State changing unexpectedly
The basic fix is:
private void OnEnable()
{
EventManager.OnScoreChanged += UpdateScore;
}
private void OnDisable()
{
EventManager.OnScoreChanged -= UpdateScore;
}
But what if the event is only supposed to be subscribed once?
Then OnEnable() may not be the right place.
For example:
private void Awake()
{
EventManager.OnScoreChanged += UpdateScore;
}
private void OnDestroy()
{
EventManager.OnScoreChanged -= UpdateScore;
}
This pattern ties the subscription to the object's lifetime rather than its enabled state.
The right approach depends on what you want:
Subscribe while enabled?
[OnEnable ](https://learngamestutorial.com/unity-onenable-function-us/)→ Subscribe
OnDisable → Unsubscribe
Subscribe for the object's lifetime?
Awake → Subscribe
OnDestroy → Unsubscribe
Choose the lifecycle that matches the actual requirement.
5. Null-Reference Problems
OnEnable() can run earlier than you expect, especially when a dependency hasn't been initialized yet.
For example:
[SerializeField]
private UIManager uiManager;
private void OnEnable()
{
uiManager.Refresh();
}
If uiManager hasn't been assigned, this can result in a NullReferenceException.
The same problem can happen when an object referenced by the component has already been destroyed or isn't available during a particular lifecycle transition.
Avoid assuming dependencies always exist
If a reference is required, make that requirement explicit.
For example:
[SerializeField]
private UIManager uiManager;
private void Awake()
{
if (uiManager == null)
{
Debug.LogError("UIManager is not assigned.", this);
}
}
Then keep OnEnable() focused on the work that should happen when the component becomes active:
private void OnEnable()
{
if (uiManager != null)
{
uiManager.Refresh();
}
}
However, don't blindly add null checks everywhere. A null check can hide an actual setup problem.
If a dependency is mandatory, failing loudly during initialization can make the bug much easier to find.
A Practical Pattern
For many event-driven components, this is a good starting pattern:
public class ScoreDisplay : MonoBehaviour
{
[SerializeField]
private ScoreManager scoreManager;
private void Awake()
{
if (scoreManager == null)
{
Debug.LogError("ScoreManager is not assigned.", this);
}
}
private void OnEnable()
{
if (scoreManager == null)
return;
scoreManager.OnScoreChanged += UpdateScore;
}
private void OnDisable()
{
if (scoreManager == null)
return;
scoreManager.OnScoreChanged -= UpdateScore;
}
private void UpdateScore(int score)
{
Debug.Log($"Score: {score}");
}
}
Here, initialization happens in Awake(), while the event subscription follows the component's enabled state.
When the component is disabled, it stops listening. When it becomes enabled again, it subscribes again.
The Key Takeaway
OnEnable() and OnDisable() are primarily state-transition callbacks, not one-time initialization and destruction callbacks.
When working with them, ask yourself:
- Can this component be enabled more than once?
- Does every subscription have a matching unsubscribe?
- Do I need cleanup on disable, or only on destruction?
- Could this dependency be null when
OnEnable()runs? - Should this code run every time the component becomes active?
If you keep those questions in mind, many of the most common lifecycle bugs in Unity become much easier to prevent.
A simple mental model is:
Awake
↓
Start
↓
OnEnable ←────────────┐
↓ │
Component is active │
↓ │
OnDisable ────────────┘
↓
OnDestroy
Treat OnEnable() and OnDisable() as a pair whenever you're managing temporary subscriptions or listeners, and your components will be much easier to reason about.
If you like the article, Please like and share your valuable feedback. If something is not clear feel free to ask in comment.
Top comments (0)