Skip to content

Published: 9/26/2026

5 Unity Coding Habits Worth Rethinking

When you’re learning Unity, tutorials are one of the easiest ways to get your first game working. I have done my share of tutorials, so I know firsthand that they can be a great resource.

So, you find an example that does something similar to what you’re trying to build, copy the general approach, make a few changes, and suddenly everything works.

That’s a good thing.

But there’s a downside: tutorial code can teach you habits without explaining why those habits exist.

Some of those habits are perfectly reasonable. Others work for a small example but aren’t approaches you necessarily want to carry into a larger project.

This article looks at five common examples where Unity developers often have a better alternative available.

The goal isn’t to create a list of rules like “never do this.” Or to poke any fun at any of the tutorial authors. Instead, it’s to understand why you might choose one approach over another.


1. Comparing Tags With ==

You will often see code like this:

if (gameObject.tag == "Player")
{
    // Do something
}

This works, but Unity provides a method specifically for checking a GameObject’s tag:

if (gameObject.CompareTag("Player"))
{
    // Do something
}

Unity’s documentation provides an API call specifically for checking whether a GameObject has a particular tag. Using the Unity API will generally perform better.

So if you’re checking a tag, CompareTag() communicates your intention more clearly:

“Check whether this GameObject has this tag.”

rather than treating the tag as an ordinary string property.

Why does this matter?

At first glance, this might seem like an insignificant difference. And for a small project, the difference may not be noticeable at all.

However, Unity knows that tags are a special part of its object system, so it provides an API specifically for working with them.

Here are three benefits you get from using the API call

  • No Memory Allocation: Accessing gameObject.tag using the equality operator pulls a new copy of the string from Unity’s native code into managed C# memory every single time. This creates garbage collection pressure. CompareTag() avoids creating this garbage entirely.
  • Speed: Because it avoids string creation and allocation, CompareTag() runs significantly faster, especially when called frequently like inside an Update or collision method.
  • Safety : CompareTag() checks if the tag actually exists in your project settings and throws an error if it does not, helping you catch typos early. Some IDE’s like JetBrains Rider will highlight a warning in the editor if the tag is not part of the saved project.

You don’t need to memorize every Unity API call. But when Unity provides a method for a common operation, it’s worth knowing that it exists.

Use CompareTag() when you’re checking a GameObject’s tag.


2. Finding Objects by Name

Another common approach is to find a GameObject by its name:

GameObject player = GameObject.Find("Player");

This is becoming less common, but I see this in many older Unity tutorials. Personally, I don’t like any kind of string lookup, but I know this can be convenient. It creates a fragile dependency between your code and the name of an object in your scene.

For example, your script now assumes that an object is named exactly:

Player

If someone changes the name in the Hierarchy to:

Player Character

The lookup no longer finds it. If you are a solo dev, that someone will often be you, forgetting your code setup.

Unity’s documentation states that GameObject.Find() returns null when no GameObject with the specified name exists. Unity also notes that GameObject.Find() can cause significant performance degradation at scale and isn’t recommended for performance-critical code, particularly in Update().

The bigger problem for a beginner, though, is often the fragile reference.

You can instead establish the relationship explicitly:

[SerializeField] private GameObject player;

Now the reference can be assigned through the Inspector.

The script is saying:

“I need a reference to a player.”

rather than:

“Go search the scene for something called Player.”

That distinction becomes important as your project grows.

What about the newer Find APIs?

Unity 6 also provides APIs such as FindFirstObjectByType<T>() and FindAnyObjectByType<T>() for situations where you genuinely need to search loaded objects by type. Unity describes these searches as resource-intensive, and FindAnyObjectByType is provided when you don’t need a specific instance.

The most common reason that you may need to search for an object is when a prefab needs a reference to an object that only exits in the Scene. For example, you may need to reference a GameManager object that is not part of your Enemy prefab. In that case you can’t use a [SerlializeField] inside the prefab because the object is not part of the prefab itself.

So in this scenario you have to do a search. That said, it’s best to do that search one time. When the scene starts. See more below.

These APIs are useful tools, but they don’t change the underlying lesson:

Don’t make searching the scene your default way of establishing object references.

When a relationship can be established directly, an explicit reference is often clearer and less fragile.


3. Calling GetComponent() Repeatedly

GetComponent() is an extremely common and useful Unity API.

For example:

Rigidbody2D rb = GetComponent<Rigidbody2D>();

There’s nothing wrong with that.

The problem is repeatedly looking up the same component when you could retrieve it once and keep the reference.

For example:

private void Update()
{
    GetComponent<Rigidbody2D>().linearVelocity = velocity;
}

I have actually seen new beginner-focused tutorials in 2026 do this. This call is really expensive and should be called in Awake() or Start() if possible so that you can save the reference.

private Rigidbody2D rb;

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

Then use the reference:

private void FixedUpdate()
{
    rb.linearVelocity = velocity;
}

The lesson here is :

“If you repeatedly need the same component, retrieve it once and keep the reference.”


4. Spawning Coroutines with yield return new WaitForSeconds()

Almost every Unity tutorial does this :

IEnumerator SpawnWaves() {
    while(true) {
        yield return new WaitForSeconds(2.0f); // Allocates memory!
        SpawnEnemy();
    }
}

I have to admit, I do this one all the time.

This works, new WaitForSeconds() instantiates a completely new object in managed memory every single time the loop runs, spiking the Garbage Collector (GC). Generally, you want to avoid Garbage Collection in any frame by frame or time-based loops.

using System.Collections;
using UnityEngine;

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] private GameObject enemyPrefab;
    [SerializeField] private float spawnDelay = 2.0f;

    // Cache variable to hold the reference
    private WaitForSeconds delay;

    private void Start()
    {
        // Allocate the memory ONCE here
        delay = new WaitForSeconds(spawnDelay);
        
        StartCoroutine(SpawnWaves());
    }

    private IEnumerator SpawnWaves()
    {
        while (true)
        {
            //  Efficient: Reuses the same exact memory pointer
            yield return delay; 
            SpawnEnemy();
        }
    }

    void SpawnEnemy()
    {
        // Spawning logic here
    }
}

5. Assuming GetComponent() Can’t Return null

Here’s a smaller mistake that can cause a very confusing error.

Suppose you write:

Rigidbody2D rb = GetComponent<Rigidbody2D>();

rb.linearVelocity = velocity;

This assumes that the GameObject has a Rigidbody2D.

But GetComponent<Rigidbody2D>() returns null if the requested component isn’t found.

That means this:

rb.linearVelocity = velocity;

will fail if rb is null.

One solution is to make the dependency explicit with RequireComponent:

[RequireComponent(typeof(Rigidbody2D))]
public class PlayerMovement : MonoBehaviour
{
    // ...
}

Unity’s RequireComponent attribute automatically adds the required component when the script is added to a GameObject, helping prevent this particular setup mistake.

Another option is TryGetComponent() when the component may legitimately not exist:

if (TryGetComponent<Rigidbody2D>(out Rigidbody2D rb))
{
    // We found the component.
}

Unity provides TryGetComponent() specifically for attempting to retrieve a component while checking whether it exists.

Which approach you use depends on the relationship.

If your script cannot function without a particular component, RequireComponent may be appropriate.

If the component is optional, TryGetComponent() may make more sense.

The important part is recognizing that a component lookup isn’t automatically guaranteed to succeed.


These Aren’t Rules to Memorize

It’s tempting to turn articles like this into a list of rules:

Before writing code, consider questions like:

Does Unity provide an API specifically for this operation? Am I using an object’s name as something my code depends on? Do I need to search for this object, or could I provide the reference directly? Am I repeatedly looking up a component that I could retrieve once? Do I know the type I’m looking for? Is this component required, or is it optional? Am I allocating memory unnecessarily?

These questions will serve you much better than memorizing a list of “good” and “bad” Unity code.

And that’s one of the reasons it’s useful to look at tutorial code critically.

A tutorial might show you code that works perfectly for the example being demonstrated. That doesn’t necessarily mean it’s the only way to solve the problem—or the approach you should automatically use everywhere.

When you see something in a tutorial and think:

“This works, but why did they do it this way?”

that’s often a great question to ask.

If you need the scripts in a downloadable format, you can check out my GitHub.

Let me know if you have any questions!


If you find this content useful please consider supporting me.

☕ Buy Me a Coffee