Coroutines

A coroutine is a method that can pause and continue later. Use one when something should happen over time or after a delay: fading the screen, a countdown, spawning waves of enemies.

The problem coroutines solve

A normal method runs from top to bottom in a single frame. This does not work:

void Explode()
{
    Debug.Log("3...");
    // There is no "wait 1 second" here. Everything below happens instantly
    // (and Thread.Sleep would freeze the entire game).
    Debug.Log("2...");
    Debug.Log("1...");
    Debug.Log("Boom!");
}

With a coroutine:

using System.Collections;
using UnityEngine;

public class Bomb : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(Countdown());
    }

    IEnumerator Countdown()
    {
        for (int i = 3; i > 0; i--)
        {
            Debug.Log(i + "...");
            yield return new WaitForSeconds(1f); // pause here for 1 second
        }
        Debug.Log("Boom!");
        Destroy(gameObject);
    }
}
Frame 1Frame 2…Frame 61Frame 62…Frame 121Update() (all scripts)Countdown() coroutineLog(3)Log(2)Log(1)…parked: WaitForSeconds(1)parked(at 60 FPS, one second is 60 frames; the game keeps running and rendering while the coroutine waits)
The Countdown() example over time. Each piece of the coroutine (highlighted) runs inside an ordinary frame; between pieces it is parked while Update and rendering carry on. The first piece runs inside Start, before the first Update. Later pieces resume after Update.

How to write one

Reading a book with a bookmark. A normal method is reading a whole book in one sitting: nobody else gets the room until you finish. A coroutine reads a few pages, puts in a bookmark (yield return), and gives the room back to Unity so the game can keep running. Later, Unity hands you the book again and you continue from the bookmark, remembering exactly where you were. WaitForSeconds(2) is saying "hand it back to me in two seconds".

  1. Add using System.Collections; at the top.
  2. Make the method return IEnumerator.
  3. Put yield return ... wherever you want to pause.
  4. Start it with StartCoroutine(MyMethod()). Calling MyMethod() directly does nothing.

What you can yield

Yield thisContinues
yield return null;Next frame (after Update)
yield return new WaitForSeconds(2f);After 2 seconds of game time. Affected by Time.timeScale, so it never finishes while paused with timeScale 0.
yield return new WaitForSecondsRealtime(2f);After 2 real seconds, even when the game is paused
yield return new WaitForFixedUpdate();After the next physics step
yield return new WaitForEndOfFrame();After the frame is rendered (for screenshots)
yield return new WaitUntil(() => isReady);When the condition becomes true (checked every frame)
yield return new WaitWhile(() => isBusy);When the condition becomes false
yield return StartCoroutine(Other());When the other coroutine has finished
yield break;Never. Ends the coroutine right away (like return).

What happens at runtime

  1. StartCoroutine runs your method immediately, up to the first yield return.
  2. Unity stores where it stopped and what it's waiting for.
  3. Every frame, Unity checks whether the wait is over. For yield return null and WaitForSeconds this check happens right after all Update calls.
  4. When the wait is over, the method continues from the exact line where it paused, with all local variables intact.
  5. This is not multithreading. Everything still runs on the main thread, one piece per frame. A slow loop without a yield will still freeze the game.

Example: fade an object out over time

using System.Collections;
using UnityEngine;

public class FadeOut : MonoBehaviour
{
    [SerializeField] private float duration = 1.5f;

    public void StartFade()
    {
        StartCoroutine(Fade());
    }

    IEnumerator Fade()
    {
        // The material's shader must support transparency for alpha to have an effect.
        Material mat = GetComponent<Renderer>().material;
        Color c = mat.color;
        float t = 0f;

        while (t < duration)
        {
            t += Time.deltaTime;
            c.a = Mathf.Lerp(1f, 0f, t / duration);
            mat.color = c;
            yield return null; // wait one frame, then loop again
        }

        gameObject.SetActive(false);
    }
}

Example: spawn waves forever

using System.Collections;
using UnityEngine;

public class WaveSpawner : MonoBehaviour
{
    [SerializeField] private GameObject enemyPrefab;
    [SerializeField] private float delayBetweenWaves = 5f;

    private Coroutine spawnRoutine;
    private readonly WaitForSeconds shortWait = new WaitForSeconds(0.5f); // reuse to avoid garbage

    void OnEnable()  { spawnRoutine = StartCoroutine(SpawnLoop()); }
    void OnDisable() { if (spawnRoutine != null) StopCoroutine(spawnRoutine); }

    IEnumerator SpawnLoop()
    {
        int wave = 1;
        while (true) // fine inside a coroutine because we yield inside the loop
        {
            for (int i = 0; i < wave * 3; i++)
            {
                Instantiate(enemyPrefab, transform.position, Quaternion.identity);
                yield return shortWait;
            }
            wave++;
            yield return new WaitForSeconds(delayBetweenWaves);
        }
    }
}

Stopping coroutines

Common Mistakes

Unity 6: AwaitableUnity 2023.1 and newer (including Unity 6) also support async/await using the Awaitable class, for example await Awaitable.WaitForSecondsAsync(1f);. Coroutines are still fully supported and are the most common approach in tutorials.

See also