OOP Notes
Master runtime polymorphism through method overriding — virtual functions, dynamic dispatch, vtables, and how the compiler resolves method calls at runtime.
Introduction
Method overriding is the mechanism that makes runtime polymorphism possible. While overriding simply means redefining a parent class method in a child class, the real power lies in what happens during execution — the program decides which version of the method to call based on the actual object type, not the reference type. This is called dynamic dispatch, and it is the foundation of flexible, extensible object-oriented design.
In this guide, we focus on method overriding as the enabler of runtime polymorphism, exploring how languages implement dynamic dispatch, what virtual functions are, how vtables work internally, and how to design systems that leverage polymorphic behavior effectively.
Understanding Runtime vs Compile-Time Polymorphism
Polymorphism means "many forms." In OOP, it manifests in two ways:
| Aspect | Compile-Time (Static) | Runtime (Dynamic) |
|---|---|---|
| Mechanism | Method overloading | Method overriding |
| Binding | Early binding (at compile time) | Late binding (at runtime) |
| Decision based on | Method signature (parameters) | Actual object type |
| Performance | Faster (resolved at compile time) | Slight overhead (vtable lookup) |
| Flexibility | Limited | Highly extensible |
| Keyword | Same method name, different params | virtual/override/@Override |
Runtime polymorphism through method overriding allows you to write code that works with base class references but automatically calls the correct derived class implementation.
How Dynamic Dispatch Works
When you call an overridden method through a base class reference, the runtime system determines which implementation to execute:
// The magic of runtime polymorphism
Animal animal = new Dog(); // Reference type: Animal, Object type: Dog
animal.speak(); // Calls Dog's speak(), NOT Animal's speak()
// The compiler doesn't know which speak() to call at compile time
// The JVM resolves this at RUNTIME based on the actual objectThe Virtual Method Table (vtable)
Most languages implement dynamic dispatch using a vtable — a lookup table of method pointers that each object carries:
| fields (name, age, etc.) | speak() → Dog.speak | |
|---|---|---|
| eat() → Animal.eat | ||
| fields (name, age, etc.) | speak() → Cat.speak | |
| eat() → Animal.eat |
When animal.speak() is called, the runtime:
- Follows the object's vtable pointer
- Looks up the
speak()entry in the vtable - Calls the function at that address
This is why it's called "late binding" — the actual method address isn't known until the program runs.
Implementing Runtime Polymorphism
Java Implementation
C++ Implementation with Virtual Functions
In C++, you must explicitly declare methods as virtual to enable dynamic dispatch:
Key C++ rules:
- Without
virtual, C++ uses static binding (base class method always called) - Always declare destructors
virtualin polymorphic base classes - Use
overridekeyword to catch typos and signature mismatches - Pure virtual functions (
= 0) make a class abstract
Python Implementation
Python uses dynamic dispatch by default — all methods are virtual:
Design Patterns Using Runtime Polymorphism
Strategy Pattern
Runtime polymorphism enables swapping algorithms at runtime:
interface SortStrategy {
void sort(int[] data);
}
class QuickSort implements SortStrategy {
@Override
public void sort(int[] data) { /* quicksort implementation */ }
}
class MergeSort implements SortStrategy {
@Override
public void sort(int[] data) { /* mergesort implementation */ }
}
class Sorter {
private SortStrategy strategy; // Base type reference
public void setStrategy(SortStrategy s) { this.strategy = s; }
public void performSort(int[] data) {
strategy.sort(data); // Dynamic dispatch picks the right algorithm
}
}Template Method Pattern
The base class defines the algorithm skeleton, derived classes fill in the steps:
abstract class DataProcessor {
// Template method - defines the algorithm
public final void process() {
readData(); // Polymorphic call
transformData(); // Polymorphic call
writeOutput(); // Polymorphic call
}
protected abstract void readData();
protected abstract void transformData();
protected abstract void writeOutput();
}Rules of Method Overriding
| Rule | Java | C++ | Python |
|---|---|---|---|
| Same method signature required | ✓ | ✓ | ✓ |
| Return type | Same or covariant | Same or covariant | Any (duck typing) |
| Access modifier | Same or wider | Same | N/A |
| Virtual by default | ✓ (all methods) | ✗ (need virtual) | ✓ (all methods) |
| Can override static methods | ✗ (hidden, not overridden) | ✗ | ✗ |
| Can override final/sealed methods | ✗ | ✗ (final) | Technically yes |
| Annotation/keyword | @Override (optional) | override (optional) | None needed |
Common Pitfalls
Pitfall 1: Calling Virtual Methods in Constructors
class Parent {
Parent() {
display(); // DANGER: Calls Parent.display(), not Child.display()
}
void display() { System.out.println("Parent"); }
}
class Child extends Parent {
private String name = "Child";
@Override
void display() { System.out.println(name); } // name is still null here!
}
new Child(); // Prints "null" — object not fully constructed yetRule: Never call overridable methods from constructors.
Pitfall 2: Forgetting Virtual Destructor (C++)
class Base {
// ~Base() {} // BUG: Non-virtual destructor with polymorphic class
virtual ~Base() {} // CORRECT: Always virtual for polymorphic classes
};Pitfall 3: Accidental Hiding Instead of Overriding
class Base {
virtual void process(int x) { }
};
class Derived : public Base {
void process(double x) { } // This HIDES, not overrides! Different signature
void process(int x) override { } // This correctly overrides
};Performance Considerations
Dynamic dispatch has a small runtime cost:
- vtable lookup: Extra pointer dereference (typically 1-2 nanoseconds)
- Cannot be inlined: Virtual calls prevent compiler optimization
- Cache impact: Indirect calls may cause instruction cache misses
For most applications, this overhead is negligible. Only optimize away polymorphism in extremely performance-critical inner loops (millions of calls per second).
Interview Questions
Q1: What is the difference between method overloading and method overriding?
Answer: Overloading is compile-time polymorphism — same method name with different parameter types/counts in the same class. The compiler resolves which version to call based on arguments. Overriding is runtime polymorphism — same method signature redefined in a derived class. The JVM/runtime resolves which version to call based on the actual object type at runtime.
Q2: How does the JVM implement dynamic dispatch internally?
Answer: The JVM uses a virtual method table (vtable) for each class. Every object has a pointer to its class's vtable. When a virtual method is called, the JVM follows the vtable pointer, looks up the method's index in the table, and jumps to the function address stored there. This allows O(1) method resolution regardless of inheritance depth.
Q3: Can you override a static method?
Answer: No. Static methods belong to the class, not to instances, so there is no object type to determine at runtime. In Java, if a subclass defines a static method with the same signature as the parent, it's called method hiding — the version called depends on the reference type, not the object type. This is resolved at compile time.
Q4: Why should destructors be virtual in C++ polymorphic classes?
Answer: When you delete an object through a base class pointer (delete basePtr), a non-virtual destructor calls only the base class destructor, causing memory leaks in derived class resources. A virtual destructor ensures the correct derived class destructor is called first, followed by base class destructors in reverse order of construction.
Q5: Explain the super keyword in the context of method overriding.
Answer: super allows a derived class to call the parent's version of an overridden method. This is useful when you want to extend the parent's behavior rather than completely replace it. For example, super.save() might handle basic persistence while the override adds validation before calling super.
Quick Revision Notes
- Runtime Polymorphism = Correct method chosen at runtime based on actual object type
- Virtual Function = Method that can be overridden and uses dynamic dispatch
- vtable = Per-class table of function pointers for virtual method resolution
- Late Binding = Method address resolved during execution, not compilation
- @Override = Annotation that tells compiler to verify the method actually overrides
- Abstract Method = Must be overridden; class cannot be instantiated directly
- Covariant Return = Override can return a more specific type than parent
- Never call virtual methods in constructors — object not fully constructed
Summary
Method overriding is not just about redefining methods — it is the mechanism that powers runtime polymorphism, enabling flexible and extensible software design. Through virtual functions, vtables, and dynamic dispatch, programs can work with abstract interfaces while the correct implementation is selected automatically at runtime. This principle underlies every major design pattern and framework in object-oriented programming.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Method Overriding and Runtime Polymorphism.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Object Oriented Programming (OOP) topic.
Search Terms
object-oriented-programming, object oriented programming (oop), object, oriented, programming, polymorphism, method, overriding
Related Object Oriented Programming (OOP) Topics