AnyLearn
All lessons
Programmingintermediate

Unity: the script lifecycle and execution order

When your Unity code actually runs. Covers the Awake, OnEnable, Start ordering guarantees, the split between Update, FixedUpdate, and LateUpdate, why deltaTime is mandatory, how coroutines fit the frame, and the allocation habits that turn a smooth game into a stuttering one.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 9

You do not call your code, Unity does

A Unity script has no main. You write a class deriving from MonoBehaviour and give it methods with specific names, and the engine calls them at defined moments. Nothing invokes Update but the engine, and it finds these methods by convention, not by an interface you implement.

public class Mover : MonoBehaviour
{
    private void Awake()   { }  // object loaded
    private void OnEnable() { } // became enabled
    private void Start()   { }  // first frame it is enabled
    private void Update()  { }  // every rendered frame
}

They can be private, which surprises people: Unity calls them through its own machinery, so access modifiers do not matter. That also means a typo is silent. Write Updte() or void update() and you get no error and no call, just a script that mysteriously does nothing. It is the single most common beginner bug in Unity.

Full lesson text

All 9 steps on one page, for reading, reference, and search.

Show

1. You do not call your code, Unity does

A Unity script has no main. You write a class deriving from MonoBehaviour and give it methods with specific names, and the engine calls them at defined moments. Nothing invokes Update but the engine, and it finds these methods by convention, not by an interface you implement.

public class Mover : MonoBehaviour
{
    private void Awake()   { }  // object loaded
    private void OnEnable() { } // became enabled
    private void Start()   { }  // first frame it is enabled
    private void Update()  { }  // every rendered frame
}

They can be private, which surprises people: Unity calls them through its own machinery, so access modifiers do not matter. That also means a typo is silent. Write Updte() or void update() and you get no error and no call, just a script that mysteriously does nothing. It is the single most common beginner bug in Unity.

2. Awake, OnEnable, Start: the setup order

Three callbacks run before the first frame, and the differences matter.

  • Awake runs once when the object loads, even if the component is disabled. This is where you set up your own state and cache references.
  • OnEnable runs every time the component becomes enabled, so it can fire many times over an object's life. Subscribe to events here.
  • Start runs on the first frame the component is enabled, and only ever once.

The guarantee that makes this usable: every Awake in the scene runs before any Start. So the rule is cache in Awake, use in Start.

private Rigidbody rb;
private void Awake() => rb = GetComponent<Rigidbody>();   // build myself
private void Start() => rb.AddForce(Vector3.up * 10f);    // safe: everyone is built

Reach into another script from Awake and you may touch something that has not initialized yet.

3. Update, FixedUpdate, LateUpdate

Three loops run repeatedly, on two different clocks.

  • Update runs once per rendered frame. Its rate is whatever the machine manages: 144 times a second on a gaming PC, maybe 30 on a phone. Input and general logic go here.
  • FixedUpdate runs on a fixed timestep, 0.02 seconds by default, so 50 times a second regardless of frame rate. Physics goes here. In a single frame it may run zero, one, or several times.
  • LateUpdate runs after every Update in the scene has finished. Camera follow goes here, so the camera reads positions that are already final for this frame.

The reason LateUpdate exists is ordering: if your camera followed the player in Update, it might read the player's position from before the player moved, and the picture jitters. Doing it in LateUpdate guarantees you see the settled frame.

4. deltaTime, or your game runs at the wrong speed

Because Update fires at whatever rate the hardware allows, any per-frame change must be scaled by elapsed time or the game literally plays faster on better hardware.

// WRONG: 5 units per frame. 144 fps machine moves ~5x faster than a 30 fps one.
transform.position += Vector3.forward * 5f;

// RIGHT: 5 units per second, on every machine.
transform.position += Vector3.forward * 5f * Time.deltaTime;

Time.deltaTime is the seconds elapsed since the previous frame. Multiplying by it converts per frame into per second, which is what you actually meant.

Inside FixedUpdate use Time.fixedDeltaTime, which is the constant timestep. And note Time.deltaTime already returns fixedDeltaTime when called from FixedUpdate, so it is not a bug there, just less explicit. A pause is Time.timeScale = 0f, which freezes deltaTime and stops FixedUpdate entirely, while Time.unscaledDeltaTime keeps running for menus.

5. Order between scripts is not guaranteed

Unity guarantees the order of phases: all Awake before all Start, all Update before any LateUpdate. It does not guarantee the order of scripts within a phase. If Spawner.Start and Level.Start both run, which goes first is undefined and can change.

This produces the classic intermittent null reference: it works, then breaks after you reopen the project, because the order silently changed.

Three fixes, in order of preference:

  1. Remove the dependency. Cache in Awake, use in Start. This solves most cases.
  2. Initialize lazily. Have the consumer ask for the thing when it first needs it, rather than assuming it exists.
  3. Force the order. Edit > Project Settings > Script Execution Order pins specific scripts before or after the default group.

Reach for option 3 last. An explicit order list is invisible to anyone reading the code and becomes a maintenance trap as the project grows.

6. Coroutines: pausing across frames

A coroutine is a method that can suspend and resume on a later frame, which is how you express do this over time without a state machine in Update.

private IEnumerator FadeOut()
{
    yield return new WaitForSeconds(1f);   // resume ~1s later
    for (float t = 0f; t < 1f; t += Time.deltaTime)
    {
        SetAlpha(1f - t);
        yield return null;                 // resume next frame
    }
    yield return new WaitForFixedUpdate(); // resume after physics
}

StartCoroutine(FadeOut());

yield return null waits one frame; WaitForSeconds waits in scaled time, so Time.timeScale = 0 pauses it.

The trap: a coroutine is owned by the MonoBehaviour that started it. Disable that component or deactivate its GameObject and the coroutine stops permanently, without running the rest of the method. It does not resume when you re-enable. Cleanup you put after a yield may therefore simply never happen.

7. Update runs 100+ times a second, so allocate nothing

Code in Update runs tens of thousands of times a minute. Anything that allocates on the managed heap creates garbage, and garbage collection eventually pauses the frame. One dropped frame is a visible stutter.

// BAD: every frame
private void Update()
{
    var rb = GetComponent<Rigidbody>();          // component lookup
    var enemies = FindObjectsOfType<Enemy>();    // scene scan + array alloc
    label.text = "Score: " + score;              // new string every frame
}

// GOOD
private Rigidbody rb;
private void Awake() => rb = GetComponent<Rigidbody>();
private void SetScore(int s) => label.text = $"Score: {s}";   // only when it changes

The usual culprits are string concatenation, LINQ, FindObjectsOfType, and anything returning a new array or list. Do not guess which: the Profiler has a GC.Alloc column that names the exact call allocating per frame. Profile on the real device, since the Editor's own overhead masks what the phone actually feels.

8. Choosing the right callback

Put work in the wrong loop and you get jitter, missed input, or physics that fights itself.

TaskCallbackWhy
cache referencesAwakeruns before every Start
subscribe to eventsOnEnable / OnDisablepairs on every enable cycle
read another scriptStarteverything is built
read inputUpdatemust not miss a key press
apply forces, move a RigidbodyFixedUpdatematches the physics clock
camera followLateUpdatetargets already moved

The input gotcha deserves its own note. Input.GetKeyDown is true for exactly one frame. Poll it in FixedUpdate, which may not run at all in a given frame, and the press is silently dropped. The standard pattern is to read input in Update, store it in a field, and consume that field in FixedUpdate.

9. One frame, start to finish

Setup runs once. Then each frame the engine may run the physics step several times, once, or not at all, before running Update, LateUpdate, and finally rendering.

flowchart TD
  A["object loads"] --> B["Awake: cache your own refs"]
  B --> C["OnEnable: subscribe"]
  C --> D["Start: first frame, everyone built"]
  D --> E["frame begins"]
  E --> F["FixedUpdate: 0, 1 or many times at 50Hz"]
  F --> G["physics solve, then collision callbacks"]
  G --> H["Update: input and logic, scale by deltaTime"]
  H --> I["LateUpdate: camera follows settled positions"]
  I --> J["render the frame"]
  J --> E

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. In a single rendered frame, how many times can `FixedUpdate` run?
    • Exactly once, always
    • Zero, one, or several times
    • Exactly twice
    • Once per child GameObject
  2. `transform.position += Vector3.forward * 5f;` runs in Update. What is wrong?
    • Vector3.forward should be Vector3.up
    • It should use localPosition instead
    • It moves 5 units per frame, so the object is faster on higher-fps machines
    • Nothing; Unity normalizes movement automatically
  3. Your script's `Update()` never runs and there is no compiler error. Most likely cause?
    • Update must be declared public
    • You must call StartCoroutine(Update()) first
    • Update only runs on objects with a Rigidbody
    • The method name is misspelled, so Unity's convention-based lookup never finds it
  4. Why read `Input.GetKeyDown` in `Update` rather than `FixedUpdate`?
    • GetKeyDown is true for only one frame, and FixedUpdate may not run that frame, dropping the press
    • Input is not accessible from FixedUpdate
    • FixedUpdate runs before input is polled by the OS
    • GetKeyDown allocates memory, which is banned in FixedUpdate
  5. You start a coroutine, then call `SetActive(false)` on its GameObject. What happens to the coroutine?
    • It pauses and resumes when the object is reactivated
    • It stops permanently, and code after the yield never runs
    • It keeps running, since coroutines are global
    • It restarts from the beginning on reactivation

Related lessons

Programming
intermediate

Unity: prefabs, assets, and serialization

How a Unity object survives being saved to disk and loaded back exactly as configured. Covers what serialization actually accepts, why GUIDs in .meta files matter more than filenames, prefabs and variants with their override rules, ScriptableObjects as shared data, and the field that keeps reverting to zero.

9 steps·~14 min
Programming
intermediate

Unity: the GameObject and component model

Unity is built on composition, not inheritance: a GameObject is an empty container, and everything it can do comes from components bolted onto it. Learn the Transform and scene graph, how components find each other, the active-versus-enabled trap, and why composition beats a deep class hierarchy for games.

9 steps·~14 min
Programming
intermediate

Unity: physics and the fixed timestep

Why Unity runs physics on its own 50Hz clock instead of the frame rate, and what follows from that. Covers rigidbodies and colliders, moving things correctly with forces rather than the Transform, interpolation for smooth motion, tunneling and continuous collision detection, and where physics cost actually comes from.

9 steps·~14 min
Programming
advanced

Async, Event Loops, and Futures

Threads are not the only model for concurrency. Learn how blocking vs non-blocking I/O works, how the event loop and reactor pattern scale to millions of connections, and how callbacks evolved into futures, promises, and async/await — plus where coroutines fit and when async loses to threads.

9 steps·~14 min