
Unity Camera Frustum Bug: Objects Culling Too Early or Late
February 10, 2026
Unity Physics Jitter Bug: Solving Teleportation and Jerky Movement
February 10, 2026OnDestroy() is one of the most important lifecycle methods in Unity. Developers rely on it to clean up resources, stop coroutines, unsubscribe from events, and release references. However, many Unity developers report situations where OnDestroy() is not called when they expect it to be, leading to memory leaks, lingering callbacks, and hard-to-debug behavior.
This article explains when and why OnDestroy() may not be called, common scenarios that cause confusion, and reliable cleanup patterns you can use to make your code safe and predictable.
What Is OnDestroy in Unity?
OnDestroy() is a MonoBehaviour callback that Unity invokes when a GameObject or component is destroyed. It is commonly used to:
- Stop coroutines
- Unsubscribe from events
- Release references
- Clean up native or unmanaged resources
Basic usage example:
void OnDestroy() {
Debug.Log("Object destroyed");
}
When used correctly, this method ensures proper cleanup and prevents memory and logic issues.
Why Developers Think OnDestroy Is Not Called
In most cases, OnDestroy() is actually working as intended. The confusion usually comes from misunderstanding Unity’s object lifecycle. Below are the most common scenarios where OnDestroy() is not called or appears to be skipped.
Scenario 1: GameObject Is Disabled, Not Destroyed
Disabling a GameObject does not trigger OnDestroy(). Only destruction does.
gameObject.SetActive(false); // OnDestroy is NOT called
In this case, Unity will call OnDisable(), not OnDestroy().
Correct pattern:
void OnDisable() {
Cleanup();
}
void OnDestroy() {
Cleanup();
}
void Cleanup() {
// Shared cleanup logic
}
Scenario 2: Application Quit Behavior
When the application quits, Unity does not guarantee that OnDestroy() will be called in a specific order. In some cases, it may not be called at all, especially on mobile platforms.
Instead, use OnApplicationQuit():
void OnApplicationQuit() {
Cleanup();
}
Never rely solely on OnDestroy() for critical shutdown logic.
Scenario 3: Domain Reload Disabled (Enter Play Mode Settings)
When Domain Reload is disabled in Unity’s Enter Play Mode settings, static variables persist between play sessions, but OnDestroy() may not behave as expected.
This often causes:
- Events staying subscribed
- Static references leaking
- Cleanup logic never running
Recommended solution:
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetStatics() {
// Reset static references here
}
Scenario 4: Script Is Removed, Not the GameObject
If a component is removed manually from a GameObject in the editor, OnDestroy() may not fire during runtime testing.
Always test destruction using code:
Destroy(GetComponent<MyComponent>());
Scenario 5: Destroyed During Scene Unload
When a scene unloads, Unity destroys objects very quickly. If other systems or managers are already destroyed, your OnDestroy() logic may throw exceptions or appear incomplete.
Example issue:
void OnDestroy() {
GameManager.Instance.Unregister(this); // GameManager already destroyed
}
Safer pattern:
void OnDestroy() {
if(GameManager.Instance != null) {
GameManager.Instance.Unregister(this);
}
}
Reliable Cleanup Patterns in Unity
1. Use OnDisable + OnDestroy Together
This ensures cleanup happens whether the object is disabled or destroyed.
void OnDisable() {
Cleanup();
}
void OnDestroy() {
Cleanup();
}
2. Explicit Cleanup Methods
Instead of relying on lifecycle callbacks, explicitly clean up before destroying objects:
public void Dispose() {
Cleanup();
Destroy(gameObject);
}
3. Unsubscribe From Events Early
Event subscriptions are one of the most common causes of bugs blamed on OnDestroy().
void OnEnable() {
EventBus.OnEvent += HandleEvent;
}
void OnDisable() {
EventBus.OnEvent -= HandleEvent;
}
This pattern is safer than unsubscribing in OnDestroy().
4. Stop Coroutines Safely
Coroutines should be stopped in both OnDisable() and OnDestroy():
void OnDisable() {
StopAllCoroutines();
}
5. Avoid Heavy Logic in OnDestroy
OnDestroy() should be lightweight. Avoid scene loading, object creation, or complex logic.
Best Practices Summary
- Do not expect
OnDestroy()to run when disabling objects. - Use
OnDisable()for most cleanup logic. - Handle application quit separately.
- Be careful with static variables and domain reload settings.
- Always null-check external dependencies.
- Unsubscribe from events as early as possible.
Conclusion
OnDestroy() is not broken, but it is often misunderstood. Many bugs attributed to “OnDestroy not being called” are actually caused by incorrect assumptions about Unity’s lifecycle. By understanding when Unity does and does not call OnDestroy(), and by using reliable cleanup patterns like OnDisable(), explicit disposal, and defensive checks, you can prevent memory leaks, dangling references, and unstable behavior.
Clean lifecycle management is essential for professional Unity development, especially in large or long-running projects.










