GDKB

Method Overloading

Beginner20 Minutes readUpdated July 21, 20263 related concepts

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`.

On this page

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) and void 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)

csharp
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);
}
csharp
weapon.Attack(goblin);              // Matches Attack(Enemy).
weapon.Attack(explosionCenter, 5f); // Matches Attack(Vector3, float).
weapon.Attack(goblin, 2f);          // Matches Attack(Enemy, float).

C++ (Unreal)

cpp
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.

csharp
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);
}
csharp
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 but Attack(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. int and float both accepting an int literal) 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.

text
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

  1. Write two overloads of a Heal method on a Health class — one taking just an int amount, another taking an int amount and a bool showEffect — and call both from a single test script.
  2. Take the DamageSystem.DealDamage example 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.
  3. You're deciding between three Spawn overloads (by prefab, by prefab + position, by prefab + position + rotation) versus one Spawn method with optional parameters (Spawn(GameObject prefab, Vector3 position = default, Quaternion rotation = default)). Argue for one approach and justify the trade-off.
  4. 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.
  • 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

Related Concepts