Method Overloading vs Overriding in OOP

Updated 2026-03-11

TL;DR

Method overloading lets you define multiple methods with the same name but different parameters (compile-time polymorphism). Method overriding lets a subclass replace a parent’s method implementation (runtime polymorphism). Overloading happens in the same class; overriding happens across inheritance hierarchies.

Prerequisites: Basic understanding of classes and objects, function definitions with parameters, and inheritance concepts. Familiarity with method calls and the idea that methods belong to classes.

After this topic: Identify when to use overloading versus overriding, implement both correctly in Python and other OOP languages, explain the difference between compile-time and runtime polymorphism, and avoid common pitfalls that cause interview failures.

Core Concept

What is Method Overloading?

Method overloading means defining multiple methods with the same name in the same class, but with different parameter lists (different number, types, or order of parameters). The compiler or interpreter determines which method to call based on the arguments you pass. This is compile-time polymorphism because the decision happens before the program runs.

Key point: Python doesn’t support traditional method overloading like Java or C++. If you define two methods with the same name, the second definition replaces the first. Python developers use default arguments or *args/**kwargs to achieve similar behavior.

What is Method Overriding?

Method overriding occurs when a subclass provides a specific implementation for a method that’s already defined in its parent class. The subclass method must have the same name, same parameters, and same return type (or a compatible subtype). This is runtime polymorphism because the actual method called depends on the object’s type at runtime.

Key point: Overriding enables you to change behavior in child classes while keeping the same interface. This is fundamental to polymorphism and the Open/Closed Principle.

Compile-Time vs Runtime Polymorphism

Compile-time polymorphism (overloading): The method signature determines which version runs. The decision is made during compilation or interpretation based on static information (the code you wrote).

Runtime polymorphism (overriding): The actual object type determines which method runs. Even if a variable is declared as a parent type, if it holds a child object, the child’s overridden method executes.

When to Use Each

Use overloading when you want the same operation to work with different input types or numbers of parameters. Example: a calculate_area() method that works for circles (one parameter: radius) and rectangles (two parameters: width, height).

Use overriding when you want to customize inherited behavior. Example: a draw() method in a Shape parent class that each child class (Circle, Rectangle) implements differently.

Visual Guide

Method Overloading Structure

classDiagram
    class Calculator {
        +add(int a, int b) int
        +add(int a, int b, int c) int
        +add(double a, double b) double
    }
    note for Calculator "Same method name,\ndifferent parameters\n(compile-time decision)"

Overloading: Multiple methods with the same name but different signatures in the same class. The compiler selects which method to call based on arguments.

Method Overriding Structure

classDiagram
    class Animal {
        +make_sound() void
    }
    class Dog {
        +make_sound() void
    }
    class Cat {
        +make_sound() void
    }
    Animal <|-- Dog
    Animal <|-- Cat
    note for Dog "Overrides parent's\nmake_sound()\n(runtime decision)"

Overriding: Subclasses replace the parent’s method implementation. The actual object type at runtime determines which method executes.

Polymorphism Decision Timeline

graph LR
    A[Write Code] --> B{Overloading?}
    B -->|Yes| C[Compile Time]
    C --> D[Signature Match]
    A --> E{Overriding?}
    E -->|Yes| F[Runtime]
    F --> G[Object Type Check]
    style C fill:#e1f5ff
    style F fill:#ffe1e1

Overloading decisions happen at compile-time based on method signatures. Overriding decisions happen at runtime based on actual object types.

Examples

Example 1: Method Overloading (Java-style concept, Python workaround)

Java/C++ approach (true overloading):

class Calculator {
    // Overloaded methods - same name, different parameters
    public int add(int a, int b) {
        return a + b;
    }
    
    public int add(int a, int b, int c) {
        return a + b + c;
    }
    
    public double add(double a, double b) {
        return a + b;
    }
}

// Usage
Calculator calc = new Calculator();
System.out.println(calc.add(5, 3));        // Output: 8 (calls first method)
System.out.println(calc.add(5, 3, 2));     // Output: 10 (calls second method)
System.out.println(calc.add(5.5, 3.2));    // Output: 8.7 (calls third method)

Python workaround (using default arguments):

class Calculator:
    def add(self, a, b, c=None):
        """Add two or three numbers."""
        if c is None:
            return a + b
        return a + b + c

# Usage
calc = Calculator()
print(calc.add(5, 3))       # Output: 8
print(calc.add(5, 3, 2))    # Output: 10

*Python workaround (using args for variable arguments):

class Calculator:
    def add(self, *args):
        """Add any number of arguments."""
        return sum(args)

# Usage
calc = Calculator()
print(calc.add(5, 3))          # Output: 8
print(calc.add(5, 3, 2))       # Output: 10
print(calc.add(1, 2, 3, 4))    # Output: 10

Try it yourself: Create a multiply method that can handle 2 or 3 numbers using default arguments.


Example 2: Method Overriding (Runtime Polymorphism)

Python implementation:

class Animal:
    def make_sound(self):
        return "Some generic sound"
    
    def describe(self):
        return f"I am an animal and I say: {self.make_sound()}"

class Dog(Animal):
    def make_sound(self):  # Overriding parent method
        return "Woof!"

class Cat(Animal):
    def make_sound(self):  # Overriding parent method
        return "Meow!"

# Usage - demonstrating runtime polymorphism
animals = [Dog(), Cat(), Animal()]

for animal in animals:
    print(animal.make_sound())
# Output:
# Woof!
# Meow!
# Some generic sound

# The actual method called depends on the object type at runtime
my_pet: Animal = Dog()  # Variable type is Animal, but object is Dog
print(my_pet.make_sound())  # Output: Woof! (Dog's version, not Animal's)
print(my_pet.describe())     # Output: I am an animal and I say: Woof!

Key observation: Even though my_pet is declared as type Animal, it calls Dog’s make_sound() method because the actual object is a Dog. This is runtime polymorphism.

Java equivalent (showing @Override annotation):

class Animal {
    public String makeSound() {
        return "Some generic sound";
    }
}

class Dog extends Animal {
    @Override  // Good practice: explicitly mark overriding
    public String makeSound() {
        return "Woof!";
    }
}

// Usage
Animal myPet = new Dog();  // Parent reference, child object
System.out.println(myPet.makeSound());  // Output: Woof!

Try it yourself: Add a Bird class that overrides make_sound() to return “Chirp!” and test it in the animals list.


Example 3: Overriding with super() - Extending Parent Behavior

class BankAccount:
    def __init__(self, balance):
        self.balance = balance
    
    def deposit(self, amount):
        self.balance += amount
        return f"Deposited ${amount}. New balance: ${self.balance}"

class SavingsAccount(BankAccount):
    def __init__(self, balance, interest_rate):
        super().__init__(balance)  # Call parent constructor
        self.interest_rate = interest_rate
    
    def deposit(self, amount):
        # Override but extend parent behavior
        result = super().deposit(amount)  # Call parent's deposit
        interest = amount * self.interest_rate
        self.balance += interest
        return f"{result}\nInterest earned: ${interest:.2f}. Final balance: ${self.balance:.2f}"

# Usage
account = SavingsAccount(1000, 0.05)
print(account.deposit(100))
# Output:
# Deposited $100. New balance: $1100
# Interest earned: $5.00. Final balance: $1105.00

Key point: Using super() lets you extend parent behavior rather than completely replacing it. This is common in real-world code.

Try it yourself: Create a CheckingAccount that overrides deposit() to charge a $1 fee on each deposit.

Common Mistakes

1. Confusing Overloading with Overriding

Mistake: Thinking you can overload methods in Python the same way as Java.

# WRONG - This doesn't create overloading in Python
class Calculator:
    def add(self, a, b):
        return a + b
    
    def add(self, a, b, c):  # This REPLACES the first add method
        return a + b + c

calc = Calculator()
print(calc.add(5, 3))  # ERROR: TypeError: add() missing 1 required positional argument: 'c'

Fix: Use default arguments or *args as shown in the examples.


2. Forgetting to Match Method Signature When Overriding

Mistake: Changing the parameter list when overriding, which creates a new method instead.

class Animal:
    def make_sound(self, volume):
        return f"Sound at volume {volume}"

class Dog(Animal):
    def make_sound(self):  # WRONG: Different signature, not overriding!
        return "Woof!"

# This doesn't override - it's a different method
dog = Dog()
dog.make_sound()      # Works: Woof!
dog.make_sound(5)     # ERROR: make_sound() takes 1 positional argument but 2 were given

Fix: Keep the same parameters. Use *args, **kwargs if you need flexibility:

class Dog(Animal):
    def make_sound(self, volume=5):  # Same signature with default
        return f"Woof at volume {volume}!"

3. Not Using super() When You Should

Mistake: Completely replacing parent behavior when you meant to extend it.

class Vehicle:
    def __init__(self, brand):
        self.brand = brand
        self.is_running = False

class Car(Vehicle):
    def __init__(self, brand, model):
        # WRONG: Forgot to call parent __init__
        self.model = model

car = Car("Toyota", "Camry")
print(car.brand)  # ERROR: AttributeError: 'Car' object has no attribute 'brand'

Fix: Always call super().__init__() when overriding __init__:

class Car(Vehicle):
    def __init__(self, brand, model):
        super().__init__(brand)  # Initialize parent attributes
        self.model = model

4. Overriding Private Methods (Name Mangling Issues)

Mistake: Trying to override methods that start with double underscores.

class Parent:
    def __private_method(self):
        return "Parent private"

class Child(Parent):
    def __private_method(self):  # This does NOT override!
        return "Child private"

# Python name mangling makes these different methods
# Parent.__private_method becomes _Parent__private_method
# Child.__private_method becomes _Child__private_method

Fix: Use single underscore for protected methods you intend to override:

class Parent:
    def _protected_method(self):  # Single underscore
        return "Parent protected"

class Child(Parent):
    def _protected_method(self):  # Now this properly overrides
        return "Child protected"

5. Assuming Overloading Works by Return Type

Mistake: In languages that support overloading (Java/C++), thinking you can overload based on return type alone.

// WRONG - This won't compile in Java
class Calculator {
    public int getValue() {
        return 42;
    }
    
    public double getValue() {  // ERROR: Method already defined
        return 42.0;
    }
}

Why it fails: The compiler can’t determine which method to call from calc.getValue() because the call syntax is identical. Overloading requires different parameter lists, not just different return types.

Interview Tips

Tip 1: Know the Terminology Cold

Interviewers often start with: “What’s the difference between overloading and overriding?” Have a crisp answer ready:

Template answer: “Overloading is compile-time polymorphism where multiple methods share a name but have different parameters in the same class. Overriding is runtime polymorphism where a subclass replaces a parent’s method implementation with the same signature. Overloading is about convenience and flexibility; overriding is about customizing inherited behavior.”


Tip 2: Explain Language Differences

If asked about Python specifically, mention: “Python doesn’t support traditional method overloading like Java or C++. The last method definition wins. We use default arguments, *args, **kwargs, or the @singledispatch decorator to achieve similar functionality.”

This shows you understand language-specific implementations, not just theory.


Tip 3: Draw the Compile-Time vs Runtime Distinction

When explaining polymorphism, emphasize the decision timeline:

  • Overloading: “The compiler looks at the method signature in the code and decides which method to call before the program runs.”
  • Overriding: “The program checks the actual object type at runtime and calls the appropriate method, even if the variable is declared as a parent type.”

Interviewers love candidates who understand when decisions happen in the program lifecycle.


Tip 4: Code the Classic Polymorphism Example

Be ready to write this pattern on a whiteboard:

class Shape:
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return 3.14 * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height

# Polymorphic behavior
shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
    print(shape.area())  # Calls the correct area() for each type

This demonstrates you understand practical overriding and polymorphism.


Tip 5: Mention the @Override Annotation (Java/C++)

If the interview is for Java or C++, mention: “I always use the @Override annotation when overriding methods. It’s not required, but it catches errors at compile-time if I accidentally create a new method instead of overriding.”

This shows you write maintainable, error-resistant code.


Tip 6: Connect to Design Principles

Link overriding to the Open/Closed Principle: “Overriding lets us extend behavior without modifying existing code. The parent class is closed for modification but open for extension through inheritance.”

Interviewers at senior levels expect you to connect OOP mechanics to design principles.


Tip 7: Know Common Pitfalls

If asked “What are common mistakes with overriding?”, mention:

  1. Forgetting to call super().__init__() in constructors
  2. Changing the method signature accidentally (creating a new method)
  3. Not understanding that private methods (double underscore in Python) can’t be overridden due to name mangling

Showing awareness of pitfalls demonstrates real-world experience.


Tip 8: Prepare for the “Why Not Just Use Different Method Names?” Question

Interviewers might ask: “Why use overriding? Why not just give each class different method names?”

Answer: “Overriding enables polymorphism. With a common interface, I can write code that works with any subclass without knowing the specific type. For example, I can iterate through a list of Shape objects and call area() on each, and the correct calculation happens automatically. This makes code more flexible and maintainable.”


Tip 9: Be Ready to Compare with Other Languages

If you list multiple languages on your resume, be prepared to explain differences:

  • Java/C++: True method overloading based on parameter types and count
  • Python: No traditional overloading; use default arguments or *args
  • JavaScript: No traditional overloading; functions can accept any number of arguments

This shows breadth of knowledge.


Tip 10: Practice the “Implement a Calculator” Question

A common interview question: “Implement a calculator that can add two or three numbers.”

In Java (overloading):

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    
    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

In Python (default arguments):

class Calculator:
    def add(self, a, b, c=0):
        return a + b + c

Practice both approaches so you can adapt to the language context.

Key Takeaways

  • Overloading is compile-time polymorphism: same method name, different parameters in the same class. Python doesn’t support it natively; use default arguments or *args instead.

  • Overriding is runtime polymorphism: a subclass replaces a parent’s method with the same signature. The actual object type at runtime determines which method executes.

  • Always use super() when overriding __init__() or when you want to extend (not replace) parent behavior.

  • Overriding enables polymorphism: you can write code that works with parent types but automatically uses child implementations, making code flexible and maintainable.

  • In interviews, clearly distinguish compile-time vs runtime decisions, know language-specific implementations, and connect overriding to design principles like the Open/Closed Principle.