Method Overloading
Summary
Method overloading lets a class declare multiple methods with the same
name, as long as their parameter lists differ — in count, type, or both.
The compiler picks the right one automatically based on the arguments
supplied at the call site. Overloading lets a single, intuitive method name
like Attack cover several related ways of doing the same conceptual
action, instead of forcing callers to remember differently-named variants
like AttackTarget, AttackTargetWithWeapon, AttackPosition.
Why This Matters
Without overloading, every variation of an operation needs its own unique
name, and those names tend to grow unwieldy fast —
SpawnEnemyAtPosition, SpawnEnemyAtPositionWithLevel,
SpawnEnemyAtPositionWithLevelAndFaction. Callers have to remember which
exact name matches the data they have on hand, and adding a new variation
means inventing yet another name that still has to somehow convey what's
different about it. Overloading collapses all of these into one name,
Spawn, with the compiler routing each call to the matching version based
on what was actually passed in.
Mental Model
Overloading is a single reception desk that reads what you brought, not several different labeled desks. Walking into an office with a form, several forms, or nothing at all, and going to the same "Submissions" desk each time is simpler than needing to already know "single form desk" versus "bulk form desk" versus "no form desk" before you arrive. The receptionist looks at what's in your hands and handles it correctly — that's the compiler matching your call's arguments to the right overload.
Core Explanation
- Overloads share a name but differ in parameter signature.
void Attack(Enemy target)andvoid Attack(Vector3 position)can coexist — the compiler distinguishes them by parameter type, not by name alone. - Return type alone does not distinguish overloads. Two methods with identical parameter lists but different return types are not valid overloads — the compiler resolves overloads purely from what's passed in, not what the caller does with the result.
- Resolution happens at compile time. The compiler determines which overload a given call matches based on the argument types at that call site — there is no runtime decision involved, unlike overriding (see [[CON-0028]]).
- Ambiguous calls are compile errors, not runtime guesses. If two overloads could both plausibly match a call (e.g. implicit numeric conversions make more than one applicable), the compiler refuses to guess and reports an ambiguity error instead.
Syntax / Theory
C# (Unity)
public class Weapon
{
public void Attack(Enemy target) =>
target.TakeDamage(baseDamage);
public void Attack(Vector3 position, float radius) =>
DealAreaDamage(position, radius, baseDamage);
public void Attack(Enemy target, float damageMultiplier) =>
target.TakeDamage(baseDamage * damageMultiplier);
}
weapon.Attack(goblin); // Matches Attack(Enemy).
weapon.Attack(explosionCenter, 5f); // Matches Attack(Vector3, float).
weapon.Attack(goblin, 2f); // Matches Attack(Enemy, float).
C++ (Unreal)
class AWeapon : public AActor
{
public:
void Attack(AEnemy* Target);
void Attack(const FVector& Position, float Radius);
void Attack(AEnemy* Target, float DamageMultiplier);
};
C++ resolves overloads with the same compile-time, argument-based matching as C#, including the same ambiguity-is-an-error rule when multiple overloads could apply through implicit conversion.
Note: C++ additionally allows default arguments (
void Attack(AEnemy* Target, float DamageMultiplier = 1.0f);), which can replace some overloads entirely — a design choice worth weighing against declaring a separate overload.
Practical Example
A logging utility benefits from overloading so callers never have to think about which specific method to call for the data they have.
public static class Logger
{
public static void Log(string message) =>
Debug.Log(message);
public static void Log(string message, Object context) =>
Debug.Log(message, context); // Highlights the object in the Unity console.
public static void Log(Exception exception) =>
Debug.LogException(exception);
}
Logger.Log("Player spawned");
Logger.Log("Enemy died", enemyGameObject);
Logger.Log(caughtException);
Every call site reads as "log this," regardless of what "this" is — the caller never needs to know or remember three separate method names.
Common Mistakes
- Overloading to hide fundamentally different behavior behind the same
name. If
Attack(Enemy target)deals damage butAttack(Vector3 position)actually moves the player, the shared name is misleading — overload only when every version performs conceptually the same operation. - Creating ambiguous overloads that force awkward casts at call
sites. Two overloads differing only by types that implicitly convert
into each other (e.g.
intandfloatboth accepting anintliteral) can force callers to cast just to disambiguate — a sign the overload set needs rethinking. - Confusing overloading with overriding. Overloading is same name, different parameters, resolved at compile time, on the same class; overriding (see [[CON-0028]]) is a subclass replacing a parent's method with the identical signature, resolved at runtime — they solve different problems.
- Using an
object-typed catch-all parameter instead of proper overloads.void Log(object thing)forces runtime type-checking (if (thing is string) ...) inside the method, throwing away the compile-time safety that separate overloads provide for free.
Game Development Example
A damage system exposes several natural ways to deal damage, all under one name so gameplay code never has to remember which specific method fits its situation.
public class DamageSystem
{
public void DealDamage(IDamageable target, int amount) =>
target.TakeDamage(amount);
public void DealDamage(IDamageable target, int amount, DamageType type) =>
target.TakeDamage(ApplyResistance(target, amount, type));
public void DealDamage(Vector3 center, float radius, int amount)
{
foreach (var target in FindTargetsInRadius(center, radius))
target.TakeDamage(amount);
}
}
A melee weapon, an elemental spell, and an explosion can each call
DealDamage with exactly the data they naturally have, without the
damage system forcing them all through one rigid parameter list.
Best Practices
- Only overload when every version performs the same conceptual operation — if the behaviors genuinely differ, give them distinct names instead.
- Keep overload sets small and clearly distinguishable; if two overloads are only distinguishable by an obscure type difference, consider naming them differently instead.
- Prefer overloading over an
object-typed or nullable-heavy parameter that requires runtime type checks inside the method body. - Document (or make self-evident through naming) what's different about each overload, since IDE autocomplete shows only the signature, not the reasoning.
Exercises
- Write two overloads of a
Healmethod on aHealthclass — one taking just anint amount, another taking anint amountand abool showEffect— and call both from a single test script. - Take the
DamageSystem.DealDamageexample and add a fourth overload for damage-over-time (DealDamage(IDamageable target, int amountPerTick, int tickCount)), and explain why this one still fits the same conceptual "DealDamage" name. - You're deciding between three
Spawnoverloads (by prefab, by prefab + position, by prefab + position + rotation) versus oneSpawnmethod with optional parameters (Spawn(GameObject prefab, Vector3 position = default, Quaternion rotation = default)). Argue for one approach and justify the trade-off. - Research why C# does not allow two methods differing only by return
type to be overloads of each other, and explain what ambiguity would
arise at a call site like
var result = DoThing();if it were allowed.
Related Concepts
- Constructors — multiple constructors on one class are overloading applied specifically to construction, following identical resolution rules.
- Method Overriding — often confused with overloading despite solving a different problem; overriding replaces inherited behavior at runtime rather than adding compile-time variants of a method.
- Functions — overloading only makes sense once you understand that a function's signature (name plus parameters) is what identifies it, not the name alone.
Further Reading
KNOWLEDGE_SCHEMA.md— GDKB's concept maturity ladder and cross-linking rules, which this document follows.- Microsoft C# documentation — Overload Resolution: https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/expressions#overload-resolution
- Unreal Engine C++ documentation — Function Overloading (C++ reference): https://dev.epicgames.com/documentation/en-us/unreal-engine/epic-cplusplus-coding-standard-for-unreal-engine