Optimizing Your Code

Practical tips for making Unity scripts run faster and stutter less. Each tip shows the slow version, the better version, and why it matters. For the overall process (budgets, profiling on devices, engine settings), see Optimization Best Practices.

Rule zero: measure first

Most code is fast enough. Before changing anything, find out what is actually slow using the Profiler (Window > Analysis > Profiler). Press Play, record a few seconds, click a spike in the CPU graph, and look at the Hierarchy view sorted by time. It tells you exactly which scripts and methods took the most time that frame.

A doctor doesn't operate before running tests. Optimizing without profiling is guessing: you might spend a day speeding up code that takes 0.01% of the frame while the real problem is somewhere else. The Profiler is your X-ray.

Profile builds, not just the EditorThe Editor adds its own overhead. For real numbers, make a Development Build with Autoconnect Profiler ticked, and profile on your target device (especially phones).

The frame budget

At 60 FPS, everything (scripts, physics, animation, rendering) must fit in 16.6 ms per frame. At 30 FPS you have 33 ms. If a single frame takes 50 ms, the player sees a stutter. So both average cost and spikes matter.

1. Cache what you look up

GetComponent, GameObject.Find, FindObjectsByType and FindWithTag search for things. Searching every frame is wasted work because the answer is almost always the same.

// SLOW: searches every frame
void Update()
{
    GetComponent<Rigidbody>().AddForce(Vector3.up);
    GameObject.Find("Player").transform.position = Vector3.zero;
}

// BETTER: search once, remember the result
private Rigidbody rb;
[SerializeField] private Transform player; // or assign in the Inspector: no search at all

void Awake()
{
    rb = GetComponent<Rigidbody>();
}

void Update()
{
    rb.AddForce(Vector3.up);
}

Looking up a friend's number in the phone book every time you call, versus saving it in your contacts. The first time is unavoidable; every time after that is wasted.

2. Avoid creating garbage every frame

C# automatically frees memory you're no longer using. This is the garbage collector (GC). It's convenient, but when it runs, it can pause your game for a moment. The less garbage you create, the less often it runs.

Using paper plates at a party. Each plate is cheap, but eventually someone has to stop and take out the trash. If you use thousands of plates a minute, that trash run happens constantly and the party keeps pausing. Reusing real plates (pooling, caching) means no trash runs.

Common sources of garbage in Update:

Creates garbageBetter
"Score: " + score every frameOnly update the text when the score changes
new List<Enemy>() every frameCreate once as a field; call list.Clear() to reuse it
LINQ (.Where(), .OrderBy(), .ToList()) in hot codeA plain for loop
Physics.RaycastAll, Physics.OverlapSphere (return new arrays)Physics.RaycastNonAlloc, Physics.OverlapSphereNonAlloc with a reused array
new WaitForSeconds(1f) inside a coroutine loopStore it in a field and reuse it
gameObject.tag == "Enemy"CompareTag("Enemy")
GetComponents<T>() (returns a new array)GetComponents<T>(list) with a reused list
Lambdas that capture local variables, created every frameNamed methods or cached delegates
// BETTER: overlap check with no garbage
private readonly Collider[] hits = new Collider[32]; // max results we care about

void CheckNearby()
{
    int count = Physics.OverlapSphereNonAlloc(transform.position, 5f, hits);
    for (int i = 0; i < count; i++)
    {
        // use hits[i]
    }
}

To find garbage, select the GC Alloc column in the Profiler's CPU Hierarchy view and sort by it.

3. Pool objects you spawn a lot

Instantiate and Destroy are expensive when done dozens of times per second (bullets, particles, damage numbers). Reuse objects with an object pool instead. Unity includes UnityEngine.Pool.ObjectPool<T>; the Object Pooling guide covers it in depth.

4. Don't run code every frame if you don't have to

// WASTEFUL: checks a slow thing 60+ times per second
void Update()
{
    FindClosestEnemy();
}

// BETTER: check 5 times per second, still feels instant for AI
private float nextCheck;

void Update()
{
    if (Time.time < nextCheck) return;
    nextCheck = Time.time + 0.2f;
    FindClosestEnemy();
}

5. Cheaper math and lookups

// Distance check without a square root
if ((a - b).sqrMagnitude < range * range) { }   // instead of Vector3.Distance(a, b) < range

// Animator parameters: hash once
private static readonly int SpeedId = Animator.StringToHash("Speed");
animator.SetFloat(SpeedId, speed);              // instead of SetFloat("Speed", speed)

// Shader/material properties: same idea
private static readonly int ColorId = Shader.PropertyToID("_BaseColor");
material.SetColor(ColorId, Color.red);

// Set position and rotation together
transform.SetPositionAndRotation(pos, rot);    // instead of setting each separately

These are small savings each, but they add up in code that runs on hundreds of objects every frame.

6. material vs. sharedMaterial

Reading renderer.material makes a copy of the material for that object the first time (so you can change its color without affecting others). Copies can't be batched together when drawing, and you must Destroy them yourself when you're done or they leak memory.

7. Physics tips

8. UI tips

9. Logging

Debug.Log is slow and still runs in builds. Remove logs from Update when you're done debugging, or wrap them:

[System.Diagnostics.Conditional("UNITY_EDITOR")]
static void EditorLog(string msg) => Debug.Log(msg); // calls are removed from builds entirely

Quick checklist

  1. Profile. Find the actual biggest cost.
  2. No Find / GetComponent in Update.
  3. No allocations per frame (check the GC Alloc column).
  4. Pool anything spawned frequently.
  5. Run slow checks on a timer or on events, not every frame.
  6. Profile again to confirm it helped.

See also