Unity OnDestroy Not Called Bug: Scenarios and Reliable Cleanup Patterns

Unity Camera Frustum Bug: Objects Culling Too Early or Late
February 10, 2026
Unity Physics Jitter Bug: Solving Teleportation and Jerky Movement
February 10, 2026
Unity Camera Frustum Bug: Objects Culling Too Early or Late
February 10, 2026
Unity Physics Jitter Bug: Solving Teleportation and Jerky Movement
February 10, 2026

Unity OnDestroy Not Called Bug: Scenarios and Reliable Cleanup Patterns

OnDestroy() 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.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *


Skip to toolbar