Quick answer: A coroutine will not start if the MonoBehaviour calling StartCoroutine is on an inactive GameObject. Unity requires the GameObject to be active for coroutines to begin. Additionally, calling StartCoroutine on a disabled MonoBehaviour component will also fail silently.

Here is how to fix Unity coroutine not starting. You call StartCoroutine(MyRoutine()) and nothing happens. No error, no warning, no execution — the coroutine simply never runs. Or worse, it starts but silently stops partway through, skipping critical logic like spawning enemies or finishing a fade-out. Coroutine failures in Unity are notoriously quiet. Here is how to track them down and fix them.

The Symptom

You have a coroutine defined as an IEnumerator method. You call StartCoroutine on it from another method, perhaps in Start(), Update(), or a button callback. The method that calls StartCoroutine runs — you can verify with a Debug.Log before it — but the coroutine body never executes. No log statements inside the coroutine appear in the Console.

Alternatively, the coroutine starts and runs its first few lines, but after a yield return new WaitForSeconds(2f) or similar yield, it never resumes. The remaining code after the yield never executes, and again, no error is shown.

In some cases, the coroutine works perfectly in one scene but fails in another, or works the first time but not on subsequent calls. This points to a state change — something is deactivating or destroying the hosting object between invocations.

What Causes This

Coroutines in Unity are bound to the MonoBehaviour instance that starts them. They are not independent threads or tasks. Several things can prevent them from running:

1. The GameObject is inactive. This is the most common cause. If the GameObject that owns the MonoBehaviour is inactive (gameObject.SetActive(false) or inactive in the hierarchy), StartCoroutine will silently fail. Unity does not throw an exception — the coroutine simply never begins. This catches many developers off guard because the calling code appears to execute normally.

2. The MonoBehaviour is disabled. Even if the GameObject is active, a disabled MonoBehaviour (enabled = false) cannot start new coroutines. Existing coroutines that were started while enabled will continue to run, but no new ones can be started. This distinction between "active GameObject" and "enabled component" is a frequent source of confusion.

3. The GameObject is destroyed mid-coroutine. If something calls Destroy(gameObject) while a coroutine is yielding, the coroutine terminates immediately at the next yield point. The code after the yield never runs. There is no finally block or cleanup mechanism — the coroutine simply vanishes.

4. StopAllCoroutines or StopCoroutine is called elsewhere. Another script (or even the same script in a different method) may be calling StopAllCoroutines(), which kills every coroutine on that MonoBehaviour. If you have multiple coroutines running and one calls StopAllCoroutines(), all of them die — including the ones you need.

5. The coroutine method has no yield statement. A method that returns IEnumerator but contains no yield statement is technically valid C# but behaves as a synchronous method that returns an empty iterator. StartCoroutine on such a method runs the body immediately (before the next frame) and completes. If you expected it to span multiple frames, the lack of a yield is the problem.

The Fix

Step 1: Verify the GameObject is active and the MonoBehaviour is enabled before starting.

using UnityEngine;
using System.Collections;

public class WaveSpawner : MonoBehaviour
{
    void Start()
    {
        // Diagnostic check before starting the coroutine
        if (!gameObject.activeInHierarchy)
        {
            Debug.LogError("Cannot start coroutine: GameObject is inactive.", this);
            return;
        }

        if (!enabled)
        {
            Debug.LogError("Cannot start coroutine: MonoBehaviour is disabled.", this);
            return;
        }

        StartCoroutine(SpawnWaves());
    }

    IEnumerator SpawnWaves()
    {
        Debug.Log("Coroutine started");

        for (int wave = 0; wave < 5; wave++)
        {
            Debug.Log($"Spawning wave {wave + 1}");
            SpawnEnemies(wave);
            yield return new WaitForSeconds(3f);
        }

        Debug.Log("All waves complete");
    }

    void SpawnEnemies(int waveIndex)
    {
        // Spawning logic here
    }
}

If the "Coroutine started" log appears but "All waves complete" does not, the coroutine is being interrupted between yields — move to Step 2.

Step 2: Protect against mid-coroutine destruction by hosting coroutines on persistent objects.

If the object running the coroutine might be destroyed (enemy dies, UI panel closes, scene transitions), move the coroutine to a persistent manager object.

using UnityEngine;
using System.Collections;

public class CoroutineRunner : MonoBehaviour
{
    private static CoroutineRunner instance;

    public static CoroutineRunner Instance
    {
        get
        {
            if (instance == null)
            {
                var go = new GameObject("CoroutineRunner");
                instance = go.AddComponent<CoroutineRunner>();
                DontDestroyOnLoad(go);
            }
            return instance;
        }
    }

    public Coroutine Run(IEnumerator routine)
    {
        return StartCoroutine(routine);
    }
}

// Usage from any script, even on objects that might be destroyed:
public class Enemy : MonoBehaviour
{
    public void Die()
    {
        // This coroutine survives even after the enemy is destroyed
        CoroutineRunner.Instance.Run(DeathSequence());
        Destroy(gameObject);
    }

    IEnumerator DeathSequence()
    {
        // Spawn particles, play sound, update score, etc.
        SpawnDeathEffect();
        yield return new WaitForSeconds(0.5f);
        GameManager.Instance.AddScore(100);
    }

    void SpawnDeathEffect()
    {
        // Particle effect instantiation
    }
}

The CoroutineRunner singleton lives on a DontDestroyOnLoad object, so its coroutines survive scene changes and object destruction. Any script can use CoroutineRunner.Instance.Run() to start a coroutine that outlives its caller.

Step 3: Store coroutine references and stop them explicitly instead of using StopAllCoroutines.

using UnityEngine;
using System.Collections;

public class UIFadeController : MonoBehaviour
{
    private Coroutine fadeCoroutine;
    private CanvasGroup canvasGroup;

    void Awake()
    {
        canvasGroup = GetComponent<CanvasGroup>();
    }

    public void FadeIn(float duration)
    {
        // Stop only the previous fade, not ALL coroutines
        if (fadeCoroutine != null)
        {
            StopCoroutine(fadeCoroutine);
        }
        fadeCoroutine = StartCoroutine(FadeRoutine(1f, duration));
    }

    public void FadeOut(float duration)
    {
        if (fadeCoroutine != null)
        {
            StopCoroutine(fadeCoroutine);
        }
        fadeCoroutine = StartCoroutine(FadeRoutine(0f, duration));
    }

    IEnumerator FadeRoutine(float targetAlpha, float duration)
    {
        float startAlpha = canvasGroup.alpha;
        float elapsed = 0f;

        while (elapsed < duration)
        {
            elapsed += Time.deltaTime;
            canvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, elapsed / duration);
            yield return null;
        }

        canvasGroup.alpha = targetAlpha;
        fadeCoroutine = null;
    }
}

By caching the Coroutine reference, you can cancel a specific coroutine without affecting others. Setting the reference to null when the coroutine completes naturally lets you check whether a coroutine is currently running.

Why This Works

Active state checks catch the most common failure mode immediately. Unity’s coroutine system is intentionally silent about failures — it does not want to spam your console every time a disabled object tries to start a routine. But that silence makes debugging difficult. Explicit checks turn silent failures into actionable log messages.

Persistent runner objects decouple the coroutine’s lifetime from the caller’s lifetime. Coroutines are not truly asynchronous — they are cooperative iterators pumped by the Unity main loop. They are bound to the MonoBehaviour that started them. Moving them to a persistent host means they run until completion regardless of what happens to the original object.

Explicit coroutine references prevent the "shotgun approach" of StopAllCoroutines(). When you have multiple independent coroutines (a fade, a timer, a spawn loop), stopping one should not kill the others. Storing references gives you precise control over each coroutine’s lifecycle.

"Coroutines fail silently. If you are not logging at the start and end of every coroutine during development, you are debugging blind."

Related Issues

If your coroutine runs but the component it references throws a NullReferenceException, the problem may be a missing component rather than a coroutine issue — see NullReferenceException on GetComponent. If your coroutine drives an animation but the Animator is not playing the expected state, check Animation Not Playing in Animator for state machine configuration issues.

Add Debug.Log at the first line of every coroutine. You will thank yourself later.