Polymorphism
"Many shapes, one draw() call." Polymorphism is the OOP pillar that makes code extensible without endless if/else chains, and interviewers love it because it reveals whether you understand dynamic dispatch โ not just memorized definitions.
The core idea
Polymorphism ("many forms") lets a single interface represent different underlying types, so the same call does the right thing depending on the actual object. You write code against a general type (Shape) and call a method (area()); at runtime the specific implementation (Circle's or Rectangle's) runs. This lets you add new types without changing the calling code.
Beginner โ the two families
- Runtime (dynamic) polymorphism โ via method overriding and inheritance/interfaces. A base-type reference points to a derived object, and the overridden method is chosen at runtime (dynamic dispatch). This is the "real" OOP polymorphism.
- Compile-time (static) polymorphism โ via method overloading (same name, different parameter lists) and generics/templates. The right method is picked at compile time by signature.
Intermediate โ how dynamic dispatch works
With overriding, the method executed depends on the object's actual (runtime) type, not the reference's declared type. Under the hood this uses a virtual method table (vtable): each object knows which concrete method to jump to. This is why a List of Shape can hold Circles and Squares and call area() uniformly โ each dispatches to its own implementation.
Advanced โ polymorphism as the engine of extensibility
Polymorphism is what makes the Open/Closed Principle practical: to support a new Shape you add a class, not edit a switch statement. It also enables dependency injection and strategy patterns. Contrast with overloading (compile-time, same class, resolved by argument types) and note it's distinct from overriding (runtime, across the hierarchy). Beware: overusing type checks (instanceof / switch on type) is usually a sign you should be using polymorphism instead.
Worked example
A drawing app keeps a list of Shape objects. Iterating and calling shape.area() on each, a Circle returns pirr and a Rectangle returns w*h โ the correct method is chosen per object at runtime. Adding a Triangle later just means a new class implementing area(); the rendering loop is untouched. Compare with overloading: a single class might define add(int,int) and add(double,double) โ the compiler picks by argument types.
When to use
Use runtime polymorphism whenever you have a family of types that share an interface but behave differently, and you want to add new types without touching existing code โ replacing type-based conditionals with method dispatch. Use overloading for convenience variants of an operation on different argument types.
Tricks and mnemonic
Remember "one interface, many implementations โ the object decides." Overriding = runtime (across classes); overloading = compile-time (same class, different signatures).
Don't confuse overriding with overloading. Overriding is runtime polymorphism (a subclass redefines a parent method, same signature); overloading is compile-time (same method name, different parameter lists in the same class). They are commonly mixed up in interviews.
- โ- Polymorphism = one interface, many forms; the same call runs the right implementation.
- โ- Runtime (dynamic) = overriding + dynamic dispatch (vtable), by actual object type.
- โ- Compile-time (static) = overloading + generics, resolved by signature.
- โ- Enables adding new types without changing callers (Open/Closed Principle).
- โ- Overriding (runtime, cross-class) โ overloading (compile-time, same class).
- โPolymorphism lets one interface stand for many implementations so the same call dispatches to the correct method for the actual object. Runtime polymorphism (overriding + dynamic dispatch) is the OOP workhorse enabling extensible, condition-free code; it's distinct from compile-time overloading resolved by signature.
Polymorphism โ Revision Notes
Fast revision of polymorphism.
- Polymorphism = "many forms": one interface, and the same call runs the right implementation for the actual object.
- Runtime (dynamic): via method overriding + inheritance/interfaces; the method is chosen by the object's actual type at runtime (dynamic dispatch, vtable).
- Compile-time (static): via method overloading (same name, different parameters) and generics/templates; resolved by signature at compile time.
- Lets you add new types without changing callers โ powers the Open/Closed Principle, strategy, and DI.
- Overriding (runtime, across classes, same signature) โ overloading (compile-time, same class, different signatures).
- Overusing instanceof/switch-on-type is a smell โ use polymorphism instead.
Polymorphism โ Flashcards
Cover the answer, recall, then check. 8 cards.
Q1. What does polymorphism mean?
A1. "Many forms" โ one interface where the same call runs different implementations depending on the actual object.
Q2. What enables runtime polymorphism?
A2. Method overriding via inheritance/interfaces, resolved by dynamic dispatch at runtime.
Q3. What enables compile-time polymorphism?
A3. Method overloading (and generics/templates), resolved by signature at compile time.
Q4. In dynamic dispatch, which type decides the method that runs?
A4. The object's actual (runtime) type, not the reference's declared type.
Q5. Overriding vs overloading?
A5. Overriding = runtime, subclass redefines a parent method (same signature); overloading = compile-time, same name with different parameter lists.
Q6. What data structure typically implements dynamic dispatch?
A6. A virtual method table (vtable).
Q7. Which SOLID principle does polymorphism make practical?
A7. Open/Closed โ add new types (classes) without modifying existing calling code.
Q8. What smell suggests you should use polymorphism?
A8. Repeated instanceof / switch-on-type conditionals selecting behavior by type.
Polymorphism โ Worked Example
Worked Example
Problem: Using the Animal/Dog/Cat classes (each overriding sound()), what does this print, and what OOP mechanism is at work?
Animal[] zoo = { new Dog(), new Cat(), new Animal() };
for (Animal a : zoo)
print(a.sound());
(Dog.sound() = "Woof", Cat.sound() = "Meow", Animal.sound() = "...")
Solution: Although every element is typed as Animal, the method that actually runs is chosen at runtime based on the object's real class โ this is runtime (dynamic) polymorphism via method overriding.
Iterating the array:
- Dog object โ "Woof"
- Cat object โ "Meow"
- Animal object โ "..."
Output:
Woof
Meow
...
The same call a.sound() produces different behaviour depending on the concrete object โ "one interface, many forms".
Answer: It prints Woof, Meow, ... โ resolved by runtime polymorphism (dynamic dispatch).
- โ- Runtime polymorphism: the object's actual type (not the reference type) picks the method.
- โ- Overriding enables dynamic dispatch; overloading (same name, different params) is compile-time.
- โ- It lets you write general code over a base type that adapts to each subclass.