← Open in the full interactive course (progress tracking, search & more)

Two methods with the same name? That's overloading. A derived class changing a base method? That's overriding.

Imagine you're building a calculator. You want a method called Add that can work with two integers, two decimals, or even three numbers. You don't want to remember AddInts, AddDecimals, AddThree — you just want one name: Add.

Now imagine you're building a payment system. You have a base PaymentProcessor class, and you want a PayPalProcessor that processes payments differently but still follows the same contract.

Overloading and overriding are two of the most important tools in object-oriented programming. They both involve methods with the same name, but they solve fundamentally different problems. Understanding the difference — and when to use each — is essential for writing clean, maintainable C# code.

What Is It?

The Simple Explanation

Overloading — multiple methods in the same class that share the same name but have different parameters. The compiler decides which one to call based on the arguments you provide.

Overriding — a derived class redefines a method from its base class using the override keyword. The runtime decides which implementation to execute based on the actual object type.

The Technical Definition

Overloading

Overriding

Why Does It Exist?

Overloading — The Problem

Without overloading, you'd need separate method names for every variation:

int AddInts(int a, int b) { ... }
double AddDoubles(double a, double b) { ... }
int AddThreeInts(int a, int b, int c) { ... }
decimal AddDecimals(decimal a, decimal b) { ... }

This is hard to remember, hard to read, and makes your API feel clumsy. You want one intuitive name that works naturally with different types and arities.

Overloading — The Solution

Overloading lets you use a single, meaningful name for a family of related operations. The compiler figures out which one to use based on the arguments at compile time.

Overriding — The Problem

Imagine you have a base class with a method that does something useful, but you need slightly different behavior in a derived class. Without overriding, you'd have to hide the base method with new (which breaks polymorphism) or use completely different method names (which defeats inheritance).

Overriding — The Solution

Overriding allows a derived class to replace the base implementation while keeping the same method signature. This enables runtime polymorphism — you can write code that works with the base type, but the derived type's behavior is used at runtime.

Big Picture

OVERLOADING vs OVERRIDING — AT A GLANCE
OVERLOADING
OVERRIDING
Same name, different parameters
Same name, same parameters
Compile-time decision
Runtime decision
Same class or inheritance
Requires inheritance + virtual/override
Console.WriteLine() has 19 overloads
ToString() is overridden by every type

How It Works

Overloading — Compile-Time Resolution

HOW OVERLOADING WORKS
1. YOU CALL A METHOD

e.g. Add(5, 10)

2. COMPILER INSPECTS ARGUMENTS
3. COMPILER FINDS BEST MATCH
4. COMPILER GENERATES CALL TO THAT OVERLOAD

Overriding — Runtime Resolution

HOW OVERRIDING WORKS
1. BASE CLASS DEFINES A VIRTUAL METHOD
public virtual string GetGreeting() => "Hello";
2. DERIVED CLASS OVERRIDES IT
public override string GetGreeting() => "G'day";
3. YOU CALL THE METHOD ON A BASE-TYPE REFERENCE
Base obj = new Derived();
obj.GetGreeting();
4. RUNTIME LOOKS AT ACTUAL OBJECT TYPE
5. RESULT

Simple Example

Overloading

public class Calculator
{
    // Overload 1: two ints
    public int Add(int a, int b) => a + b;

    // Overload 2: two doubles
    public double Add(double a, double b) => a + b;

    // Overload 3: three ints
    public int Add(int a, int b, int c) => a + b + c;

    // Overload 4: two decimals
    public decimal Add(decimal a, decimal b) => a + b;
}

// Usage
var calc = new Calculator();
Console.WriteLine(calc.Add(5, 10));        // 15 (int, int)      → overload 1
Console.WriteLine(calc.Add(5.5, 2.3));     // 7.8 (double, double) → overload 2
Console.WriteLine(calc.Add(1, 2, 3));      // 6 (int, int, int)   → overload 3
Console.WriteLine(calc.Add(5.99m, 2.01m)); // 8.00 (decimal, decimal) → overload 4

Code → Meaning → Result

Overriding

public class Animal
{
    public virtual string Speak() => "Some animal sound";
}

public class Dog : Animal
{
    public override string Speak() => "Woof!";
}

public class Cat : Animal
{
    public override string Speak() => "Meow!";
}

// Usage
Animal a1 = new Animal();
Animal a2 = new Dog();
Animal a3 = new Cat();

Console.WriteLine(a1.Speak());  // "Some animal sound"
Console.WriteLine(a2.Speak());  // "Woof!"   ← runtime decides!
Console.WriteLine(a3.Speak());  // "Meow!"   ← runtime decides!

Code → Meaning → Result

Real-World Example

Imagine an e-commerce system with different payment processors. Overloading and overriding work together to make the system flexible and clean.

// ─── Base class with a virtual method ───
public abstract class PaymentProcessor
{
    public abstract bool ProcessPayment(decimal amount);

    // Overloaded helper methods
    public bool ProcessPayment(decimal amount, string currency)
    {
        // Convert currency if needed, then call the main overload
        return ProcessPayment(ConvertToBaseCurrency(amount, currency));
    }

    public bool ProcessPayment(decimal amount, string currency, string description)
    {
        LogPayment(description);
        return ProcessPayment(amount, currency);
    }

    protected virtual void LogPayment(string description)
    {
        Console.WriteLine($"Log: {description}");
    }

    private decimal ConvertToBaseCurrency(decimal amount, string currency) { ... }
}

// ─── Derived classes override the core method ───
public class StripeProcessor : PaymentProcessor
{
    public override bool ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Stripe: processing {amount:C}");
        // Stripe API call...
        return true;
    }
}

public class PayPalProcessor : PaymentProcessor
{
    public override bool ProcessPayment(decimal amount)
    {
        Console.WriteLine($"PayPal: processing {amount:C}");
        // PayPal API call...
        return true;
    }

    // Override logging too
    protected override void LogPayment(string description)
    {
        Console.WriteLine($"PayPal Log: {description.ToUpperInvariant()}");
    }
}

// ─── Usage ───
var processors = new List<PaymentProcessor>
{
    new StripeProcessor(),
    new PayPalProcessor()
};

foreach (var processor in processors)
{
    // Even though we only know about PaymentProcessor,
    // each processor uses its own implementation!
    processor.ProcessPayment(99.99m, "USD", "Order #1234");
}

What's happening here?

Analogy

Two mental models

Overloading — like a multi-tool. You have one tool (the method name) but it comes with several attachments (parameter signatures). You pick the right attachment for the job, and the tool behaves accordingly. The decision is made before you start working (compile time).

Overriding — like a remote control. The base class defines a button (virtual method), and each derived class decides what that button actually does (override). When you press the button, the TV (the runtime) checks what's plugged in and performs the correct action — even if you're pointing the remote at a different room (a base-type reference).

Under the Hood

RUNTIME INTERNALS
1. OVERLOADING — COMPILER GENERATES DISTINCT IL
2. OVERRIDING — VIRTUAL METHOD TABLE (VMT)
3. C# 14 / .NET 10 — NO CHANGE TO FUNDAMENTALS

Common Confusion

1. Overloading vs Overriding — The Names Are Similar, the Behaviors Are Not

This is the single biggest source of confusion. Both involve methods with the same name, but they are fundamentally different:

AspectOverloadingOverriding
Binding timeCompile-timeRuntime
SignatureDifferentSame
ScopeSame class (or inheritance)Base → Derived
KeywordsNone requiredvirtual / override / abstract

2. Hiding (new) vs Overriding (override)

Using new in a derived class hides the base method — it doesn't override it. When you call the method on a base-type reference, the base implementation runs, not the derived one. This breaks polymorphism and is almost always what you don't want in inheritance hierarchies.

public class Base
{
    public virtual void M() => Console.WriteLine("Base.M");
}
public class DerivedNew : Base
{
    public new void M() => Console.WriteLine("DerivedNew.M"); // hides!
}
public class DerivedOverride : Base
{
    public override void M() => Console.WriteLine("DerivedOverride.M");
}

Base b1 = new DerivedNew();
Base b2 = new DerivedOverride();
b1.M();  // "Base.M"      ← new doesn't override
b2.M();  // "DerivedOverride.M"  ← override works

3. Overloading and Return Types

Return type alone does NOT distinguish overloads. You cannot have two methods with the same name and same parameters but different return types — the compiler can't know which one to call.

//  Does NOT compile
public int GetValue() => 42;
public string GetValue() => "42";  // Compiler error!

Common Mistakes

Mistake 1 — Forgetting override keyword

Wrong — this hides the method instead of overriding it:

public class Dog : Animal
{
    public string Speak() => "Woof!";  // Hides, doesn't override!
}

Correct — use override to get polymorphic behavior:

public class Dog : Animal
{
    public override string Speak() => "Woof!";
}

Mistake 2 — Overloading with ambiguous parameters

Creating overloads that can cause ambiguity:

public void M(int a, double b) { }
public void M(double a, int b) { }
// M(1, 2) → ambiguous! Compiler error.

Design overloads where the compiler can unambiguously choose based on argument types.

Mistake 3 — Forgetting to call base when needed

When overriding, sometimes you need the base behavior to run in addition to your new behavior. Forgetting base.Method() can break important setup logic.

public override void Dispose()
{
    //  Missing: base.Dispose();
    // Now base resources are never cleaned up!
}

When Should I Use It?

Use overloading when:

Use overriding when:

When to avoid over-using:

Mental Model

Overloading = same name, different parameters → compiler picks
Overriding = same name, same parameters → runtime picks

Remember:
· Overloading is about convenience — one name, many forms
· Overriding is about polymorphism — one interface, many behaviors
· Overloading is resolved at compile time
· Overriding is resolved at runtime
· You can overload without inheritance; you can't override without it

Key Takeaway


Check Your Understanding

You've seen how overloading and overriding work, how they differ, and when to use each. Let's see if you can apply these concepts.

1. Which of the following correctly describes the difference between overloading and overriding?

Show answer

Correct: C

Why C is correct: Overloading is about having multiple methods with the same name but different parameters. Overriding is about replacing a base class method's implementation in a derived class with the same signature. This is the core distinction.

Why A is incorrect: It's the opposite — overloading is resolved at compile time (static binding), and overriding is resolved at runtime (dynamic binding).

Why B is incorrect: Overriding requires inheritance (you must have a base class with a virtual/abstract method). Overloading does not require inheritance — you can overload methods in the same class.

Why D is incorrect: Return type alone cannot distinguish overloads, but overloads can have different return types as long as the parameter signatures differ. Overriding requires the exact same return type (or a covariant return type in some languages, but in C# it must match).

Reinforcement: Overloading = compile-time, same name different params. Overriding = runtime, same name same params with virtual/override.

2. Consider the following code. What is the output?

public class Shape
{
    public virtual void Draw() => Console.WriteLine("Shape");
}
public class Circle : Shape
{
    public override void Draw() => Console.WriteLine("Circle");
}
public class Square : Shape
{
    public new void Draw() => Console.WriteLine("Square");
}

Shape s1 = new Circle();
Shape s2 = new Square();
s1.Draw();
s2.Draw();
Show answer

Correct: B

Why B is correct: Circle uses override, so when called on a Shape reference, the runtime calls Circle.Draw. Square uses new (hiding), so when called on a Shape reference, the base Shape.Draw runs.

Why A is incorrect: This would happen if s2 was declared as Square instead of Shape. But here the variable is a Shape reference, and new does not override.

Why C and D are incorrect: They incorrectly predict Shape for s1, but Circle uses override so polymorphism works.

Reinforcement: override enables runtime polymorphism; new hides the base method and does not.

3. Which of the following method signatures can coexist as overloads in the same class?

Show answer

Correct: D

Why D is correct: Both B and C demonstrate valid overloading. B changes the parameter type (int vs double). C changes the parameter count (2 vs 3).

Why A is incorrect: These two methods have the exact same signature (M(int, int)) and differ only by return type. Return type alone cannot distinguish overloads in C# — the compiler cannot know which one to call.

Why C is not the only correct answer: C is valid, but B is also valid, so D is the best answer.

Reinforcement: Overloads must differ by parameter type, count, or order — not by return type alone.

4. In a real application, you have a base ReportGenerator class with a virtual Generate() method. You need a PdfReportGenerator that produces PDF output and a CsvReportGenerator that produces CSV. You want to write code that works with ReportGenerator references but gets the correct format. Which approach should you use?

Show answer

Correct: B

Why B is correct: Overriding is the correct tool when you want derived classes to replace the behavior of a base class method while keeping the same signature. This enables runtime polymorphism — you can write code that works with ReportGenerator and the correct generation happens automatically.

Why A is incorrect: Overloading with different parameters would break the contract — callers would need to know which overload to use, defeating the purpose of polymorphic design.

Why C is incorrect: new hides the base method — when calling through a base reference, the base implementation runs, not the derived one. This breaks polymorphism.

Why D is incorrect: Creating separate methods defeats the purpose of inheritance and forces callers to know about each concrete type.

Reinforcement: When you need different behavior in derived classes while keeping a common interface, override is the way to go.

5. You're designing a logging library. You want a Log method that can accept a string message, a message with an exception, or a message with structured data. Users should be able to call logger.Log("Hello"), logger.Log("Error", ex), and logger.Log("Event", new { UserId = 123 }). What should you use?

Show answer

Correct: C

Why C is correct: In a real logging library, you typically use overloading to provide convenience methods for callers (different parameter combinations). Inside those overloads, you call a single core Log method that is virtual so that derived loggers (e.g., FileLogger, DatabaseLogger) can override the core behavior while preserving the convenient overloads. This is a common and powerful pattern.

Why A is incorrect: Overriding alone doesn't give you multiple parameter combinations — you'd need overloading for that.

Why B is incorrect: Overloading alone works for the convenience part, but if you want different logger implementations (e.g., file, database, console) to handle logging differently, you also need overriding.

Why D is incorrect: Separate method names are less intuitive and force callers to remember different names. The whole point of overloading is to keep a consistent, discoverable API.

Reinforcement: Overloading provides convenience at the call site. Overriding provides polymorphic behavior across derived classes. They are not mutually exclusive — they work beautifully together.

You now have a solid mental model of overloading and overriding — you can distinguish them, use them correctly, and explain why they exist!


dotnetmadeeasy.com — Learn C# and .NET, the right way.