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.
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.
virtual / override requiredvirtual in base and override in derivedabstract methods (must be overridden in non-abstract derived classes)base.Method()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 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.
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 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.
virtual/override
Console.WriteLine() has 19 overloads
ToString() is overridden by every type
e.g. Add(5, 10)
int, intAdd with matching signaturepublic virtual string GetGreeting() => "Hello";
public override string GetGreeting() => "G'day";
Base obj = new Derived();
obj.GetGreeting();
"G'day" even though variable type is Basepublic 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
Add — each with a different signature.Add with whatever numbers you have.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
Animal defines a virtual method — a placeholder.Dog and Cat override it with their own implementation.Animal, the runtime calls the correct overridden method based on the actual object type.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?
ProcessPayment implementation. The system works with PaymentProcessor references, but the correct processor is used at runtime.ProcessPayment method has multiple overloads (amount only, amount+currency, amount+currency+description). This gives callers flexibility while keeping the name consistent.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).
Add(Int32, Int32) and Add(Double, Double) are distinct methods in ILThis is the single biggest source of confusion. Both involve methods with the same name, but they are fundamentally different:
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
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!
override keywordWrong — 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!";
}
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.
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!
}
Console.WriteLine has 19 overloads so you can print almost anything)ToString(), Equals(), or GetHashCode() in your custom typesealed for classes/methods that shouldn't be extendedvirtual in the base and override in the derived class — don't forget them.base.Method() to call the base implementation when you need to extend, not replace, behavior.new) is not overriding — it breaks polymorphism and is rarely what you want.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?
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();
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?
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?
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?
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.