Here is why you don't understand C# interfaces

Here is why you don't understand C# interfaces

TL;DR: Most beginners meet C# interfaces as a textbook definition and never in real gameplay code. An interface is a promise of behavior. I show how an interaction system replaces messy if-chains of GetComponent, GetComponent, GetComponent, why composition beats deep inheritance, and why IInteractable is a better tool than Unity tags.

Introduction

If you feel like you “kind of” understand C# interfaces but never really know when to use them, you’re not alone.
Most beginners meet interfaces only in theory: a few slides in a course, a short definition in a book, maybe an interview question. But they rarely see a real gameplay problem that interfaces actually solve. I shipped games for three years before I could answer fundamentals questions like this in an interview, and it cost me a job (how I learned Unity the wrong way).

In my tutorial on C# interfaces and Unity, I show them in the context of real game code, not just abstract syntax: how interfaces get rid of messy if‑chains, fix brittle inheritance hierarchies, and make your code easier to extend. If you haven’t watched it yet, you can find it here: C# Interfaces and Unity.

This article is the “companion” to that tutorial. We’ll look at:

  • What interfaces are really for in your games
  • Why an interaction system is the best way to learn them
  • Why interfaces and composition beat inheritance in real Unity projects
  • Why interfaces are usually a better tool than Unity tags

By the end, you should understand not just what interfaces are, but why your code gets better when you use them.

What C# interfaces are really for

Let’s ignore the textbook definition for a moment and focus on the job they do in your project.

An interface is a promise of behavior.

  • It says: “Anything that implements me will have these methods / properties.”
  • It does not say how those methods are implemented.
  • It lets other code talk to that behavior without knowing the underlying type.

In practice, that gives you three superpowers:

  1. Decoupling – your code depends on capabilities (“this thing can be interacted with”), not on specific concrete classes (“this is specifically a Door script”).

  2. Swapping implementations – you can change how something behaves (play animation, spawn particles, open UI, etc.) without touching the code that calls it.

  3. Extensibility – you can add new types that fit into existing systems without modifying the system—you just implement the right interface.

Typical beginner code in Unity looks like this:

void TryInteract()
{
    RaycastHit hit;
    if (Physics.Raycast(cameraRay, out hit, interactRange))
    {
        var door = hit.collider.GetComponent<Door>();
        var chest = hit.collider.GetComponent<Chest>();
        var npc = hit.collider.GetComponent<NpcDialogue>();

        if (door != null) door.Open();
        else if (chest != null) chest.Open();
        else if (npc != null) npc.StartDialogue();
    }
}

This works… until you add more interactable objects. The method grows longer and more fragile.

With an interface, the player only cares about “this object can be interacted with”:

public interface IInteractable
{
    void Interact();
}

Now Door, Chest, NpcDialogue etc. implement the interface:

public class Door : MonoBehaviour, IInteractable
{
    public void Interact()
    {
        // Play door animation, sound, etc.
    }
}

And the player code becomes:

void TryInteract()
{
    RaycastHit hit;
    if (Physics.Raycast(cameraRay, out hit, interactRange))
    {
        var interactable = hit.collider.GetComponent<IInteractable>();
        interactable?.Interact();
    }
}

The player no longer needs to know what it hit. It just calls Interact() on anything that promises to be IInteractable.
That is what interfaces are for.

Why an interaction system is the best way to learn interfaces

If you’re new to interfaces, an interaction system is one of the clearest, most concrete examples you can build.

Think of all the different things a player might interact with:

  • Doors
  • Chests / loot containers
  • NPCs with dialogue
  • Switches and levers
  • Save points
  • Shops
  • Collectibles and pickups

They’re wildly different under the hood, but from the player’s point of view there’s one question:

“Can I interact with this object right now, and if yes, what should happen?”

That’s a perfect use case for an interface like IInteractable.

Step 1: define the capability

public interface IInteractable
{
    void Interact();
}

You might later add overloads with more context (like Interact(Player player)), but the idea is the same: it defines what the interaction entry point is.

Step 2: let each object decide what it does

public class Chest : MonoBehaviour, IInteractable
{
    public void Interact()
    {
        // Play open animation, spawn items, update inventory
    }
}

public class NpcDialogue : MonoBehaviour, IInteractable
{
    public void Interact()
    {
        // Show dialogue UI, start conversation
    }
}

Now your player script stays tiny and stable while the variety of interactables grows:

void TryInteract()
{
    if (!Physics.Raycast(cameraRay, out var hit, interactRange))
        return;

    var interactable = hit.collider.GetComponent<IInteractable>();
    if (interactable == null)
        return;

    interactable.Interact();
}

Whenever you want a new interactable object, you don’t touch the player code at all. You just implement the interface on the new component.
That feedback loop “add a new object, don’t touch the system code”—is what makes interfaces click for most beginners.

Why interfaces (and composition) are better than inheritance here

A lot of Unity beginners try to solve the interaction problem with inheritance:

public abstract class Interactable : MonoBehaviour
{
    public abstract void Interact();
}

public class Door : Interactable
{
    public override void Interact() { /* ... */ }
}

public class Chest : Interactable
{
    public override void Interact() { /* ... */ }
}

This can work, but it has hidden costs:

  1. You only get one base class – Unity already forces you to inherit from MonoBehaviour. If you base everything on Interactable, you quickly run into “what if this thing also needs to inherit from some other base?” problems.

  2. You tend to stuff too much into the base class – beginners often keep adding fields and logic to the base Interactable to support different use cases. Over time, every subclass pays the cost for things it doesn’t actually need.

  3. Hierarchy becomes rigid – when you realize you structured the hierarchy badly, refactoring can be painful. Moving behavior around means touching lots of files.

Interfaces push you toward composition over inheritance:

  • The “interaction capability” lives in the interface.
  • The actual behavior lives in a component that implements that interface.
  • GameObjects are built by composing components, not by climbing an inheritance tree.

You might still have a small abstract base class here and there, but your core axis of design is: “What can this object do?”, not “What is this object in the class hierarchy?”.

In other words:

  • Use inheritance sparingly, for shared implementation details.
  • Use interfaces widely, for shared contracts and capabilities.

Why interfaces are better than Unity tags for this

Another common beginner solution is using tags:

void TryInteract()
{
    if (Physics.Raycast(cameraRay, out var hit, interactRange))
    {
        if (hit.collider.CompareTag("Door"))
        {
            hit.collider.GetComponent<Door>().Open();
        }
        else if (hit.collider.CompareTag("Chest"))
        {
            hit.collider.GetComponent<Chest>().Open();
        }
        // and so on...
    }
}

This has several problems:

  1. Strings and magic values – tags are just strings. Typos and rename mistakes happen quietly and show up at runtime.
  2. No real contract – the tag "Door" doesn’t guarantee anything about components or methods. You still have to assume that a Door script exists and has the right methods.
  3. Limited expressiveness – a GameObject only has one main tag. You can’t say “this is interactable and also an enemy and also a quest object” in a structured way.
  4. Still tightly coupled – you still write a big if/else chain that knows about every specific type.

With an interface:

  • No strings: GetComponent<IInteractable>() is type‑safe.
  • Real contract: if a class claims to implement IInteractable but forgets Interact(), it won’t compile.
  • Flexible composition: a single object can implement multiple interfaces like IInteractable, IDamageable, ISaveable, etc.
  • Decoupled player code: your player doesn’t care whether it hit a door, a chest, or something you invent next month.

Tags can still be useful for some things (quick debugging filters, broad categorization), but for gameplay behavior like interactions, interfaces give you a much cleaner and safer architecture.


Conclusion

If C# interfaces have always felt abstract or academic, it’s probably because you haven’t seen them tied to a real, painful problem in your own code yet. An interaction system in Unity is one of the best places to start:

You define what “being interactable” means in an interface. Every object that can be interacted with implements that interface in its own way.
Your player code becomes tiny, decoupled, and future‑proof.

Interfaces shine when you care about what something can do, not what specific type it is. Combined with composition, they lead to cleaner, more flexible Unity projects than deep inheritance hierarchies or tag‑driven if‑chains.

Use this article together with the video tutorial C# Interfaces and Unity, and then implement a small interaction system in your own project. After you’ve added a few different interactable objects without touching your player script, you’ll understand interfaces on a much deeper level than any definition can give you.

Once interfaces click, events are the next C# fundamental most Unity devs still get wrong. Same shape of problem, same "I kind of get it" reaction before you see it in real code. Read Most Unity devs still don't understand events.