GetComponent in Unity: Caching, TryGetComponent and Performance

Illustration: a glowing component block snapping into a modular object frame

Almost every Unity script talks to other components, and almost every beginner has been told that GetComponent is “slow”. The truth is more nuanced: one call is cheap, but calling it hundreds of times per frame, or using it to search the whole scene, adds up. This guide to GetComponent in Unity covers what it actually costs, how to cache references properly, when to use TryGetComponent and RequireComponent, the traps in GetComponentInChildren, and how to find other objects without GameObject.Find.

What GetComponent Actually Costs

GetComponent<T>() walks the list of components on one GameObject and returns the first one that matches the type. A typical GameObject has a handful of components, so a single call takes well under a microsecond. Calling it once in Awake is never a problem.

Where it hurts is repetition. This pattern is very common in beginner code:

void Update()
{
    GetComponent<Rigidbody>().AddForce(Vector3.forward * 10f);
    GetComponent<Animator>().SetFloat("Speed", speed);
}

On one object it’s harmless. On 500 enemies it’s 1,000 lookups per frame that produce exactly the same answer every time. There’s a second hidden cost: if the component is missing, GetComponent in the Editor returns a “fake null” object that allocates garbage so it can give you a helpful error message later. Repeated failed lookups in Play Mode can show up as GC allocations in the Profiler even though the build doesn’t allocate.

The rule of thumb: look things up once, store the result, and reuse it. If you want to measure the difference yourself, our Unity Profiler guide shows how to find hot scripts.

Caching References in Awake

Store components in private fields the first time you need them. Awake is the right place for components on the same GameObject, because it runs before any Start and before other scripts are likely to ask for them.

using UnityEngine;

public class EnemyMover : MonoBehaviour
{
    [SerializeField] float speed = 4f;

    Rigidbody rb;
    Animator animator;
    static readonly int SpeedHash = Animator.StringToHash("Speed");

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

    void FixedUpdate()
    {
        rb.linearVelocity = transform.forward * speed;
        animator.SetFloat(SpeedHash, speed);
    }
}

Two extra wins are hiding in this snippet. Animator.StringToHash converts the parameter name once instead of hashing the string every call, and linearVelocity is the Unity 6 name for what used to be velocity. If you’re unsure whether to cache in Awake or Start, read Awake vs Start and execution order. The short version: grab your own components in Awake, talk to other objects in Start.

What about transform and gameObject?

transform and gameObject are already cached by Unity internally. Old tutorials recommended storing transform in a field, but in modern Unity the difference is negligible. Don’t bother.

Lazy caching

If a component is optional or only needed occasionally, cache it the first time it’s used:

AudioSource audioSource;
AudioSource Audio => audioSource != null ? audioSource : audioSource = GetComponent<AudioSource>();

Note the explicit != null. Avoid ??= with Unity objects because the null-coalescing operators skip Unity’s overloaded equality check and won’t notice a destroyed component.

Serialized References: the Cheapest Lookup

The fastest GetComponent is the one you never call. If a reference is known at edit time, expose it with [SerializeField] and drag it in:

[SerializeField] Health targetHealth;
[SerializeField] ParticleSystem hitEffect;

Unity resolves serialized references when the scene or prefab loads, so there’s no runtime search at all. This works especially well inside prefabs, where children reference each other and the links survive every instance. You can combine both approaches by filling the field automatically in Reset, which runs when the component is first added or when you choose Reset from its menu:

void Reset()
{
    targetHealth = GetComponent<Health>();
}

TryGetComponent for Collisions and Raycasts

Some lookups can’t be cached because you don’t know the target in advance: the thing a bullet hit, the object a raycast found, whatever walked into a trigger. For these, use TryGetComponent:

void OnTriggerEnter(Collider other)
{
    if (other.TryGetComponent(out Health health))
    {
        health.TakeDamage(10f);
    }
}

TryGetComponent returns a bool and hands back the component through an out parameter. Its main advantage over GetComponent plus a null check is that it never creates the Editor-only fake-null object when the component is missing, so it doesn’t allocate in Play Mode. It also reads more clearly.

The same pattern works with raycasts:

if (Physics.Raycast(ray, out RaycastHit hit, 100f) &&
    hit.collider.TryGetComponent(out IDamageable target))
{
    target.Damage(25f);
}

Yes, you can pass an interface type. GetComponent and TryGetComponent both accept interfaces, which lets enemies, crates and barrels all respond to damage without a shared base class.

Colliders on child objects

If the collider lives on a child (a common setup for character models), other.TryGetComponent looks on the child, not the root, and fails. Use other.attachedRigidbody to jump to the object that owns the Rigidbody, or call other.GetComponentInParent<Health>(). More trigger gotchas are in OnTriggerEnter not working.

RequireComponent

If a script can’t work without another component, say so with an attribute:

[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(AudioSource))]
public class Projectile : MonoBehaviour
{
    Rigidbody rb;
    void Awake() => rb = GetComponent<Rigidbody>();
}

When you add Projectile to an object, Unity adds the Rigidbody and AudioSource automatically and stops you removing them while Projectile is attached. Your cached GetComponent in Awake can now never return null, which removes a whole class of NullReferenceException bugs.

One catch: RequireComponent only acts when the script is added. If you add the attribute to a script that’s already on objects, existing objects aren’t updated. Remove and re-add the script, or add the missing components by hand.

GetComponentInChildren and InParent Pitfalls

These helpers search a hierarchy, which makes them slower than GetComponent and easier to get wrong.

  • They include the object itself. GetComponentInChildren<Collider>() checks the current GameObject first. If the root has a collider, that’s what you get, not the child’s.
  • Inactive objects are skipped by default. Pass true to include them: GetComponentInChildren<Weapon>(true). This catches people out with weapons or UI panels that start disabled.
  • Order is depth-first and not something to rely on. If there are several matches, which one you get depends on hierarchy order. Use a serialized reference when you need a specific one.
  • The plural versions allocate. GetComponentsInChildren<T>() returns a new array. Use the overload that fills a list you reuse:
readonly List<Renderer> renderers = new();

void Awake()
{
    GetComponentsInChildren(true, renderers);   // fills the list, no new array
}

Call these once in Awake and cache the result, exactly like single lookups.

Finding Objects Without Find

GameObject.Find("Player") searches every active object in every loaded scene by name. It’s slow, it breaks the moment someone renames the object, and it silently returns null for inactive objects. FindObjectOfType is now obsolete; Unity 6 replaces it with FindFirstObjectByType and FindAnyObjectByType (the latter is faster when you don’t care which one you get). They’re fine during setup but shouldn’t run every frame.

Better options, from simplest to most flexible:

  1. Serialized references for objects that exist in the same scene or prefab.
  2. Pass references in when spawning. The spawner already knows the player, so it can hand it over: enemy.Init(player).
  3. A static registry for things there are many of:
public class Enemy : MonoBehaviour
{
    public static readonly List<Enemy> All = new();

    void OnEnable()  => All.Add(this);
    void OnDisable() => All.Remove(this);
}

Now any script can loop over Enemy.All with zero searching. For one-of-a-kind objects like a GameManager, a simple static Instance property set in Awake does the same job. For shared data and settings, a ScriptableObject asset referenced in the Inspector is often cleaner still, and it keeps scenes loosely coupled.

📚 Want to write cleaner Unity code?

Knowing why references and lifecycles work the way they do makes every script easier to write. These are the programming and game design books we recommend to developers leveling up.

See our book picks →

Troubleshooting

GetComponent returns null even though the component is there

Check it’s on the same GameObject the script is on, not a child or parent. Also confirm you’re asking for the right type: a BoxCollider isn’t found by GetComponent<SphereCollider>(), though GetComponent<Collider>() finds either.

GetComponentInChildren can’t find a disabled child

Inactive objects are skipped unless you pass includeInactive: true.

The cached reference becomes null later

The component or its GameObject was destroyed. Unity’s overloaded == reports destroyed objects as null. Re-acquire it, or use events so the destroyed object unsubscribes itself.

TryGetComponent fails on a trigger hit

The collider is on a child object. Use other.attachedRigidbody or GetComponentInParent.

Profiler shows GC alloc from GetComponent in the Editor

That’s the Editor-only fake-null object from failed lookups. Switch to TryGetComponent or fix the missing component. It won’t appear in builds, but it hides real allocations while you profile.

FAQ

Is GetComponent slow in Unity?

A single call is fast. It only becomes a problem when called many times per frame across many objects. Cache the result in Awake and the cost disappears.

Should I use TryGetComponent or GetComponent?

Use TryGetComponent when the component might not exist, such as collision or raycast targets. Use GetComponent in Awake for components you know are there, ideally guaranteed with RequireComponent.

Can GetComponent find interfaces?

Yes. GetComponent<IDamageable>() and TryGetComponent(out IDamageable d) both work and return the first component implementing the interface.

What replaced FindObjectOfType in Unity 6?

FindFirstObjectByType and FindAnyObjectByType. Use FindAnyObjectByType when you don’t need a specific one, as it’s faster.

Is caching transform worth it?

No. Unity caches transform internally, so the gain is too small to matter.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *