Summary

Every Unity script that inherits from MonoBehaviour has access to a set of lifecycle methods that the engine calls automatically at specific moments. The two most important for beginners are Start, which runs once just before the script’s first Update (for objects already in the scene, that is when Play mode begins), and Update, which runs every frame while the script is enabled. Understanding when each method runs — and which type of logic belongs in each — is the foundation of all Unity scripting.

Key ideas

Start() — runs exactly once, on the frame the script is first enabled, just before its first Update call. For objects placed in the scene this happens as Play mode begins; for objects spawned at runtime with Instantiate, Awake and OnEnable run during the Instantiate call itself, and Start runs later, before that object’s first Update (Unity Scripting Reference, MonoBehaviour.Start and MonoBehaviour.Awake). Use it for:

  • Logging initial state to verify the script is attached and configured correctly
  • Caching component references (e.g. storing a Rigidbody2D in a field so Update can use it without calling GetComponent every frame); Awake is an equally good, and often better, place for this, because the components already exist and other scripts’ Start may then use your cached references (see “Awake order between scripts” below)
  • Any one-time initialisation

Update() — runs every frame, as long as the scene is playing and the GameObject is active. Use it for:

  • Reading player input
  • Moving objects
  • Ticking timers
  • Anything that must respond to the passage of time

Time.deltaTime — the number of seconds that elapsed since the last frame. Multiplying any per-frame value by Time.deltaTime converts it from “per frame” to “per second”, making it frame-rate independent. A machine running at 60fps calls Update 60 times per second with a small deltaTime; a machine at 30fps calls it 30 times with a larger deltaTime. The product is the same either way.

gameObject.name — the name of the GameObject this script is attached to. Including it in Debug.Log messages is useful when multiple objects share the same script.


In practice

using UnityEngine;
 
public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;
    private float logTimer = 0f;
 
    // Runs once, just before this script's first Update
    void Start()
    {
        Debug.Log("[" + gameObject.name + "] PlayerController ready. Speed: " + moveSpeed);
    }
 
    // Runs every frame
    void Update()
    {
        HandleMovement();
        LogPositionPeriodically();
    }
 
    private void HandleMovement()
    {
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        // Time.deltaTime makes movement speed-per-second, not speed-per-frame
        transform.Translate(h * moveSpeed * Time.deltaTime, v * moveSpeed * Time.deltaTime, 0f);
    }
 
    private void LogPositionPeriodically()
    {
        logTimer += Time.deltaTime;
        if (logTimer >= 2f)
        {
            Debug.Log("[" + gameObject.name + "] Position: " + transform.position);
            logTimer = 0f;
        }
    }
}

Keep Update() short: delegate logic to named private methods rather than writing everything inline. A long Update body is hard to read and debug. The HandleMovement / LogPositionPeriodically pattern above is the recommended style.

How to read this script:

  1. Read the fields first. moveSpeed is a tunable Inspector value; logTimer is remembered between frames.
  2. Read Start next. It confirms the script is attached and reports the starting configuration.
  3. Read Update as a repeated loop. Every frame it calls HandleMovement, then LogPositionPeriodically.
  4. Follow each helper method. HandleMovement changes position; LogPositionPeriodically changes the timer and occasionally logs.
  5. Check which values persist. logTimer must be a field because it accumulates over many frames; h and v can be local because they are recalculated each frame.

This mirrors the course teaching rhythm: fields store game state, Start verifies setup, and Update performs a small set of readable per-frame actions (course lab series, see source-csharp-unity-labs).


Other lifecycle methods

MethodWhen it runs
Awake()Before Start, even if the script is disabled. Use for self-initialisation that other scripts’ Start may depend on. The order between different scripts’ Awake is not defined (see below).
OnEnable() / OnDisable()When the GameObject is enabled or disabled. Useful for resetting state and for subscribing to / unsubscribing from events.
FixedUpdate()At a fixed time step (default 50 times/second). Use for physics calculations instead of Update.
LateUpdate()After all Update calls this frame. Use for camera follow logic.
OnDestroy()Just before the GameObject is destroyed. Use for cleanup.

Spawn timing: what runs during Instantiate

Objects created at runtime follow the same methods, but not all at once. When a spawner calls Instantiate on an active prefab:

  1. Inside the Instantiate call: Unity creates the clone and calls Awake and then OnEnable on its scripts (Unity Technologies, Scripting API: Object.Instantiate).
  2. Instantiate returns: the spawner’s next line runs. The clone’s components, including its Rigidbody or Rigidbody2D, already exist, so GetComponent, setting fields and calling methods on the clone all work immediately.
  3. Later, before the end of the current frame: Start runs, before the clone’s first Update. The Scripting Reference states that “Awake and Start are called on the instantiated objects before the end of the current frame” (Unity Technologies, Scripting API: MonoBehaviour.Start).
  4. Next physics step: any force added so far is applied. AddForce only accumulates the force at the time of the call; “the physics system applies the effects during the next simulation run” (Unity Technologies, Scripting API: Rigidbody.AddForce).

The practical consequence: values the spawner sets after Instantiate are not visible in the clone’s Awake or OnEnable, but they are visible in its Start.

Worked example: a bullet whose speed is set by the gun

// Gun.cs
[SerializeField] private Bullet bulletPrefab;
[SerializeField] private Transform muzzle;
 
private void Fire()
{
    Bullet b = Instantiate(bulletPrefab, muzzle.position, muzzle.rotation);
    // Bullet.Awake and Bullet.OnEnable have ALREADY run here; Bullet.Start has not.
    b.Init(20f);
}
// Bullet.cs (prefab has a Rigidbody)
public class Bullet : MonoBehaviour
{
    private Rigidbody rb;
    private float speed = 5f;                          // default, replaced by Init
 
    private void Awake() => rb = GetComponent<Rigidbody>();   // self-setup: safe here
 
    public void Init(float newSpeed) => speed = newSpeed;
 
    private void Start()
    {
        // Runs after Init, so speed is 20. Reading speed in Awake would give 5.
        rb.AddForce(transform.forward * speed, ForceMode.Impulse);
    }
}

“Do I have to wait for Update before applying forces?” No. If Awake added or cached the Rigidbody and its Collider, a one-off force (an impulse, a launch) can be applied in Start straight away; Update gains you nothing, because forces are only applied when the physics step runs anyway. Three timing facts are worth knowing:

  • Contacts come later. Collision and trigger callbacks (OnCollisionEnter, OnTriggerEnter and their 2D versions) and contact queries only have results after the first physics step has simulated the new body, so do not expect a collision in Start to be reported yet. A body that spawns overlapping something reports the contact on the first step, not at creation.
  • Continuous forces belong in FixedUpdate. A one-off impulse can go anywhere; a force you apply every tick (thrust, wind, a steering force) goes in FixedUpdate, which the Manual describes as the place for “applying a force to a Rigidbody” because physics runs on the fixed timestep (Unity Technologies, Unity Manual: Fixed updates). See unity-rigidbody2d for reading input in Update and applying it in FixedUpdate.
  • Components added in code are usable at once. A Rigidbody added with AddComponent<Rigidbody>() in Awake exists as soon as that call returns, so Start, or the next line, can use it.

“Should I use Awake or Start so the Rigidbody is ready for forces?” Either works for the physics itself: the Rigidbody exists as soon as the clone does, so cache it in Awake, and a force added in Awake, in Start or by the spawner straight after Instantiate is applied on the next physics step. Choose by data, not physics: if the force depends on something the spawner sets after Instantiate (speed, direction, owner), apply it in Start or inside the Init method itself. A common alternative is to apply the launch force inside Init, which makes the order explicit.

Caveats:

  • If the prefab (or the clone’s parent) is inactive, Awake and OnEnable do not run during Instantiate; they run when the object is first activated (Unity Technologies, Scripting API: Object.Instantiate and MonoBehaviour.Awake).
  • AddForce has no effect if the GameObject is inactive, and it does not work on a kinematic body (Unity Technologies, Scripting API: Rigidbody.AddForce).
  • In 2D, the same timing applies: use Rigidbody2D, ForceMode2D.Impulse and usually transform.right or transform.up (see unity-rigidbody2d). Spawning many bullets is a good case for pooling, where OnEnable replaces Start as the “I have just been fired” hook because Start runs only once per object.

Awake order between scripts

Unity guarantees that every Awake runs before any Start, but it does not guarantee the order in which different scripts’ Awake methods run, whether the scripts are on different GameObjects or on the same one. The Scripting Reference says “The order in which Unity calls each GameObject’s Awake is not deterministic”, so you should not “assume that a reference set up by one GameObject’s Awake will be usable in another GameObject’s Awake”; instead, “use Awake to set up references between scripts, and use Start, which is called after all Awake calls are finished, to pass any information back and forth” (Unity Technologies, Scripting API: MonoBehaviour.Awake). The Manual adds that scripts sharing the same execution order run in an order that “is not deterministic”, which can differ between builds, machines and Unity versions (Unity Technologies, Unity Manual: Script execution order).

What this means for two scripts on one prefab, say Health and HealthBar:

In HealthBar.AwakeSafe?Why
health = GetComponent<Health>();YesThe components exist before any Awake runs; you are only getting a reference.
bar.max = health.MaxHealth; where Health sets MaxHealth in its own AwakeNoHealth.Awake may not have run yet; it can work in one build and fail in another.
The same line in HealthBar.StartYesEvery Awake has finished before any Start.

Does Instantiate change this? Not the rule itself: inside Instantiate, the clone’s scripts all receive Awake (and OnEnable) before the call returns, still in an undefined order between scripts, and Start follows later in the frame. The extra trap is the one in “Spawn timing” above: values the spawner sets after Instantiate are not visible in any Awake. If the prefab or its parent is inactive, none of these Awake calls run until the object is activated.

The working rule: Awake caches your own references and initialises your own state; Start reads other scripts’ data. Only when that is not enough (a manager that must initialise before everything else), force an order:

[DefaultExecutionOrder(-100)]   // lower numbers run earlier than the default (0)
public class GameManager : MonoBehaviour { /* ... */ }

or set it in Edit > Project Settings > Script Execution Order. The Manual’s own example is configuring the order “to ensure that Awake for one script is always called before Awake for another”; if a script has both an attribute and a Project Settings value, the Project Settings value wins, and orders set with the attribute do not appear in that window (Unity Technologies, Unity Manual: Script Execution Order project settings reference; Unity Manual: Script execution order). Use it sparingly: a long list of forced orders hides dependencies that would be clearer as an explicit Init call.


Gotchas

  • Never call Start or Update yourself. Unity calls them. If you want to run initialisation code manually, extract it into a separate method and call that.
  • Variables declared inside Start or Update are local. They exist only for that call. Fields (declared at class level) persist between frames.
  • Update does not run if the GameObject is inactive or the script component is disabled.
  • Physics-based movement belongs in FixedUpdate, not Update, to avoid erratic behaviour when frame rate fluctuates.
  • Reading another script’s data in your Awake works by luck, not by design: the order between scripts’ Awake is undefined. Read it in Start.

Practice

Create a GameObject named LifecycleProbe. Add a script with:

  • one Debug.Log in Start
  • one timer in Update
  • one private method called from Update
  • one field that stores elapsed time

Success test:

  • Start logs once when Play mode begins
  • the timer logs every two seconds
  • the log includes gameObject.name
  • no local variable is expected to remember a value between frames

Extension:

  • disable the script component during Play mode and observe that Update stops running

Self-test

  1. Which method runs once: Start or Update?
  2. Why should movement usually multiply by Time.deltaTime?
  3. A variable declared inside Update resets every frame. Where should it go if it needs to remember a value?
  4. Which lifecycle method is normally better for physics movement?
  5. Why is a short Update method easier to debug?

Answers

  1. Start runs once when the script first becomes active.
  2. Time.deltaTime turns movement into units per second, so speed stays consistent across different frame rates.
  3. It should be a field, declared at class level.
  4. FixedUpdate, because Unity runs physics on a fixed timestep.
  5. A short Update points you to named helper methods, so the likely fault is easier to find.

Worked example