Method Overriding
Summary
Method overriding lets a subclass replace the implementation of a method its parent class already defines, keeping the exact same name and signature. Code that calls the method through a parent-typed reference still runs the subclass's version if the object is actually a subclass instance — the correct behavior is chosen at runtime based on the object's real type, not the type of the variable referring to it.
Why This Matters
A base Enemy class might define a generic Attack() that walks up and
swings a weapon, but a RangedEnemy needs to fire a projectile instead,
and a BossEnemy needs a multi-phase attack sequence. Without overriding,
handling this requires the calling code to check "what kind of enemy is
this?" and branch accordingly — if (enemy is RangedEnemy) ... else if (enemy is BossEnemy) ... — which has to be updated every time a new enemy
type is added. Overriding lets every enemy type supply its own Attack(),
and calling code just calls enemy.Attack() without ever needing to know
which concrete type it's dealing with.
Mental Model
Overriding is a job title with the same responsibilities, filled differently by each employee. Every "Cashier" at a store chain has the same job title and the same core responsibility — "process a transaction" — but a specific store might train its cashiers to do that slightly differently (a scan-based register versus a manual one). A customer just says "please check me out," and whichever concrete implementation of "Cashier" is actually standing there handles it in their own way, without the customer needing to know which store's specific training they received.
Core Explanation
virtualon the base marks a method as replaceable. A base class method must be declaredvirtual(or the class contains anabstractmethod — see [[CON-0029]]) before any subclass is allowed to override it; a non-virtualmethod cannot be overridden, only hidden (a distinct, usually-unwanted mechanism).overrideon the subclass replaces the implementation, keeping the same signature. The overriding method must match the base method's name, parameters, and return type exactly — this is what distinguishes it from overloading (see [[CON-0027]]), which deliberately changes the parameters.- Resolution happens at runtime, based on the object's actual type.
Enemy enemy = new RangedEnemy(); enemy.Attack();callsRangedEnemy's override, even though the variable's declared type is the baseEnemy— this runtime dispatch is the mechanism underlying polymorphism (see [[CON-0030]]). base.MethodName()calls the parent's version from inside an override. An override doesn't have to fully replace the parent's logic — it can run the base behavior first or last and add to it, rather than duplicating what the base already does.
Syntax / Theory
C# (Unity)
public class Enemy : MonoBehaviour
{
public virtual void Attack()
{
Debug.Log($"{name} swings a weapon.");
}
}
public class RangedEnemy : Enemy
{
public override void Attack()
{
Debug.Log($"{name} fires a projectile.");
}
}
public class BossEnemy : Enemy
{
public override void Attack()
{
base.Attack(); // Runs the base swing first...
Debug.Log($"{name} follows up with a ground slam.");
}
}
Enemy current = enemies[i]; // Declared type is Enemy, whatever the concrete type.
current.Attack(); // Runs the correct override for whichever type it actually is.
C++ (Unreal)
class AEnemy : public ACharacter
{
public:
virtual void Attack();
};
class ARangedEnemy : public AEnemy
{
public:
virtual void Attack() override; // 'override' is optional but strongly recommended.
};
C++ requires virtual on the base declaration exactly as C# requires
virtual; the override keyword on the derived declaration is technically
optional but should always be used — it makes the compiler verify a
matching virtual method actually exists on the base class, catching typos
that would otherwise silently create an unrelated new method instead of an
override.
Unreal Blueprint
BlueprintNativeEvent (or BlueprintImplementableEvent) on a C++ function
exposes an override point directly in the Blueprint editor — a
Blueprint-only subclass can override the C++ base's behavior via a "Event
[Name]" node, without touching C++ at all.
Practical Example
A notification system defines a base "how to display" behavior that different notification types customize.
public class Notification
{
public virtual string Format() => $"[Notice] {Message}";
public string Message;
}
public class WarningNotification : Notification
{
public override string Format() => $"[⚠ Warning] {Message}";
}
public class ErrorNotification : Notification
{
public override string Format() => $"[✖ Error] {Message.ToUpper()}";
}
List<Notification> queue = GetPendingNotifications();
foreach (var note in queue)
Console.WriteLine(note.Format()); // Correct formatting per type, no type-checking here.
The loop never asks "is this a warning or an error?" — each notification knows how to format itself, and the loop just asks for the result.
Common Mistakes
- Forgetting
virtualon the base method, which makes the subclass'soverridea compile error — a clear signal, but only after the fact; design the base class with overriding in mind from the start if subclasses are expected to customize behavior. - Omitting
overridein C++ (where it's optional). Without it, a typo'd signature silently creates a new, unrelated method instead of overriding the intended one — always includeoverrideso the compiler catches the mismatch. - Calling
base.Method()unnecessarily, duplicating the entire base implementation when the override was meant to fully replace it — only callbasewhen genuinely extending, not replacing, the inherited behavior. - Type-checking (
if (enemy is RangedEnemy)) instead of relying on overriding. If a base method could be overridden to produce the correct behavior automatically, manually branching on type is exactly the problem overriding exists to eliminate — see [[CON-0030]].
Game Development Example
A UI system's base Panel class defines default open/close animation
behavior that specific panels override.
public class Panel : MonoBehaviour
{
public virtual void Open() => canvasGroup.alpha = 1f;
public virtual void Close() => canvasGroup.alpha = 0f;
}
public class InventoryPanel : Panel
{
public override void Open()
{
base.Open();
RefreshItemList(); // Extends the base behavior instead of replacing it.
}
}
Every panel in the game can be opened and closed identically from calling
code (panel.Open()), while each panel type customizes exactly what
"open" additionally means for it.
Best Practices
- Mark a base method
virtualonly when subclasses genuinely need to customize it — marking everythingvirtualby default signals no behavior is settled, which makes the base class's contract unclear. - Always use
overrideexplicitly (mandatory in C#, optional-but-required by convention in C++) so signature mismatches are caught at compile time. - Call
base.Method()when extending inherited behavior; omit it entirely when the override is a full replacement. - Prefer overriding over type-checking (
is/aschains) whenever the differing behavior can be expressed as "each type does its own version of this one operation."
Exercises
- Create a base
Pickupclass with avirtual void OnCollected()that logs a generic message, then createHealthPickupandAmmoPickupsubclasses that each override it with type-specific behavior. - Take the
Panelexample and add aSettingsPaneloverride ofClose()that callsbase.Close()and additionally saves settings — explain why callingbase.Close()here is the right choice rather than duplicating the fade logic. - You're designing an
Abilitybase class whereCast()behavior varies significantly across 15+ ability types. Decide between one largevirtual Cast()override per ability versus composing abilities from smaller, non-overridden building-block methods, and justify the trade-off. - Research why calling a
virtualmethod from inside a base class's own constructor is considered dangerous in both C# and C++, and explain what state the overriding subclass's fields might be in at that point.
Related Concepts
- Inheritance — overriding is only possible because a subclass inherits the base class's method in the first place; it's the mechanism for customizing what's inherited.
- Method Overloading — easily confused with overriding, but solves the opposite problem: same signature vs. varying signatures, runtime resolution vs. compile-time resolution.
- Polymorphism — runtime override resolution (calling the right version through a base-typed reference) is the concrete mechanism that makes polymorphism work.
Further Reading
KNOWLEDGE_SCHEMA.md— GDKB's concept maturity ladder and cross-linking rules, which this document follows.- Microsoft C# documentation — Polymorphism (override/virtual): https://learn.microsoft.com/dotnet/csharp/fundamentals/object-oriented/polymorphism
- Unreal Engine documentation — BlueprintNativeEvent and Overriding C++ Functions in Blueprints: https://dev.epicgames.com/documentation/en-us/unreal-engine/blueprint-native-event-nodes-in-unreal-engine