Interfaces
Summary
An interface declares a set of methods a class promises to implement,
without providing any implementation itself — it is a contract, not a
blueprint. IDamageable might declare void TakeDamage(int amount); with
no body at all; any class that implements IDamageable must provide its
own TakeDamage. Interfaces let unrelated classes (Enemy, Destructible Crate, Player) share a behavioral contract without needing to inherit
from a common base class.
Mental Model
If a base class is a blueprint a house is built from, an interface is a certification a house can earn regardless of which blueprint built it: "fire-code compliant" says nothing about room layout, kitchen size, or which architect drew the plans — it only guarantees that certain requirements are met. A wooden house and a brick house, built from completely different blueprints, can both be "fire-code compliant" if each independently satisfies the certification's requirements in its own way.
This is exactly why an Enemy and a DestructibleCrate — otherwise
unrelated classes with no shared base class — can both implement
IDamageable and both be handled by the same "anything damageable" code,
without one inheriting from the other.
Core Explanation
- An interface declares signatures, not bodies.
interface IDamageable { void TakeDamage(int amount); }specifies what a conforming class must provide, never how. Each implementing class writes its ownTakeDamagebody, and different classes' bodies can (and usually do) do completely different things. - A class can implement multiple interfaces, even though (per
Inheritance) it can only inherit from one base class.
Enemymight implement bothIDamageableandIPoolableat once — this is the primary reason interfaces exist where single inheritance would be limiting: a class needs several independent behavioral contracts, not one linear ancestry. - Interfaces enable polymorphism across unrelated types. Code written
against
IDamageablecan operate on any class that implements it, regardless of what that class actually inherits from — the same polymorphic dispatch Inheritance introduced, but decoupled from a shared base class. - Interfaces vs. abstract base classes. An abstract base class (a class that cannot be instantiated directly, only inherited from) can mix declared-but-unimplemented methods with real shared implementation and real shared fields — an interface, in most mainstream languages, declares no fields and (traditionally) no implementation at all. Choose an abstract base class when there is genuine shared code and state to give derived classes; choose an interface when only the contract needs to be shared, across classes that otherwise have nothing in common.
- Naming convention. C# conventionally prefixes interface names with
I(IDamageable,IPoolable) specifically so a type name alone signals "this is a contract, not a concrete class" at the point of use.
Syntax / Examples
C# (Unity)
public interface IDamageable
{
void TakeDamage(int amount);
}
public class Enemy : IDamageable
{
private int health = 100;
public void TakeDamage(int amount) => health -= amount;
}
public class DestructibleCrate : IDamageable
{
private int hitsRemaining = 3;
public void TakeDamage(int amount)
{
hitsRemaining--;
if (hitsRemaining <= 0) Destroy(gameObject);
}
}
// Works on either class — only the contract matters here.
void ApplyExplosionDamage(IDamageable target, int amount) => target.TakeDamage(amount);
C++ (Unreal)
// Unreal's interface pattern: a UInterface + a pure C++ interface class.
UINTERFACE(MinimalAPI)
class UDamageable : public UInterface { GENERATED_BODY() };
class IDamageable
{
GENERATED_BODY()
public:
virtual void TakeDamage(int32 Amount) = 0; // pure virtual: no body, must be implemented
};
class AEnemy : public AActor, public IDamageable
{
GENERATED_BODY()
public:
virtual void TakeDamage_Implementation(int32 Amount) override { /* ... */ }
};
Unreal Blueprint
A Blueprint Interface asset declares function signatures with no
implementation, mirroring IDamageable. Any Blueprint class can "Add
Interface" from its class settings, then implement each declared
function — the Blueprint equivalent of class Enemy : IDamageable,
letting unrelated Blueprint classes share a callable contract.
Practical Example
An explosion needs to damage anything nearby that can be damaged — enemies, crates, breakable walls — regardless of what each of those classes otherwise inherits from:
void Explode(Vector3 origin, float radius, int damage)
{
foreach (Collider hit in Physics.OverlapSphere(origin, radius))
{
if (hit.TryGetComponent<IDamageable>(out var damageable))
{
damageable.TakeDamage(damage);
}
}
}
Explode never mentions Enemy or DestructibleCrate by name — it only
depends on IDamageable, so any future class that implements the
interface automatically works with this function, with zero changes to
Explode itself. This is the same "write once against a shared contract"
payoff Inheritance's polymorphism example demonstrated, extended to
classes with no common ancestor at all.
Common Mistakes
- Reaching for inheritance when an interface fits better — if two classes need to share a contract but have no other meaningful shared state or implementation, forcing a common base class purely to get polymorphism is more rigid than an interface would be.
- Interfaces that grow too large ("fat interfaces") — an interface
with many unrelated methods forces every implementing class to provide
all of them, even ones that don't apply; smaller, focused interfaces
(
IDamageable,IPoolableas separate interfaces) let a class opt into exactly the contracts it needs. - Forgetting that an interface reference only exposes the interface's
methods — a variable typed as
IDamageablecannot call a method that onlyEnemy(and not the interface) declares, even if the underlying object happens to be anEnemy; a downcast back to the concrete type is needed for that. - Assuming an interface can hold state — in most mainstream OOP languages an interface declares no fields; any shared state still needs to live in each implementing class (or a shared base class instead).
Best Practices
- Prefer an interface over inheritance when the goal is purely a shared contract across otherwise-unrelated classes.
- Keep interfaces small and focused — one coherent capability per interface, rather than one large interface covering several unrelated responsibilities.
- Name interfaces after the capability they guarantee (
IDamageable,IPoolable), not after a category of object. - Write code against the interface type, not the concrete class, whenever only the contract's behavior is actually needed — it keeps that code usable with any future implementer.
Exercises
- Define an
IPoolableinterface withOnSpawn()/OnDespawn()methods and implement it on two otherwise-unrelated classes. - Rewrite the
Explodeexample to also damage aPlayerclass, without modifyingExplodeitself — only by makingPlayerimplementIDamageable. - Explain when an abstract base class would be the better choice over an interface for a scenario you construct, and why.
- Identify a "fat interface" (write one with 5+ unrelated methods) and split it into two smaller, focused interfaces.
Related Concepts
- Inheritance — the other major mechanism for polymorphism, sharing implementation rather than just a contract.
- Objects — interface references still point at concrete objects; the interface only restricts which of that object's methods are visible through it.
References
KNOWLEDGE_SCHEMA.md— GDKB's concept maturity ladder and cross-linking rules.- C# Language Reference — Interfaces: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/interfaces
- Unreal Engine — Interfaces: https://dev.epicgames.com/documentation/en-us/unreal-engine/interfaces-in-unreal-engine