1. What Is It?
lets one reuse and extend another. You declare Dog extends Animal (Java) or class Dog(Animal) (Python), and Dog automatically gets Animal's fields and methods — plus whatever Dog adds or overrides. The goal is reuse without copy-paste, and a shared vocabulary for related types.
is the payoff: code written against the parent type works unchanged when you pass a child type. A function that takes Animal can receive any Dog, Cat, or Sparrow, and calling animal.speak() runs the right version for each. Without , every new type forces you to open up and edit every function that handles it — the classic cascade of if isinstance(...) or switch(type) blocks.
A backend service handles payments and currently has a processPayment(CreditCardPayment payment) function. The team needs to add support for PayPal and crypto payments. Without polymorphism, what is the most likely maintenance problem they will face?
2. How It Works
Inheritance: the "is-a" link
When Dog inherits from Animal, a Dog has everything an Animal has. The parent's fields and methods are present in the child, and the child can:
- Add new fields and methods
- Override a parent method to change its behavior
- Call the parent's version via
super()/super.method()/Parent::method()
# Python
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal):
def speak(self): # override
return f"{self.name} says woof"
class Puppy(Dog):
def speak(self):
return super().speak() + " (small)" # reuse parent
print(Puppy("Rex").speak()) # Rex says woof (small)Polymorphism: one call, many behaviors
# Python — duck typing; no declaration needed
def describe(animal):
print(animal.speak())
describe(Dog("Rex")) # Rex says woof
describe(Puppy("Max")) # Max says woof (small)The vtable — how dynamic dispatch works
When a method is polymorphic, the compiler can't decide at compile time which function to call — it depends on the 's actual type at runtime. C++ and Java implement this with a vtable (virtual method table). Each has a table of function pointers, and each object carries a hidden pointer to its 's table. A call like a.speak() becomes "look up speak in a's vtable, call that."
- Java: every method is dispatched through this mechanism by default. Python gets the same effect by looking the name up along the class's MRO at call time.
- C++: only methods marked
virtualare. Non-virtual calls are resolved at compile time and are faster — but won't do .
Puppy reuses Dog through super
Puppy inherits Animal’s initializer, calls Dog through super, and adds its own suffix.
Puppy inherits from Dog, which already inherits from Animal.
Text version
- Puppy inherits from Dog, which already inherits from Animal.
- The inherited initializer sets the name on this Puppy instance.
- Puppy overrides speak, so its version runs, and super calls Dog's speak.
- Dog reads the instance name and returns Rex says woof to Puppy.
- Puppy adds its small suffix to the message from Dog.
- The printed message combines parent behavior with the child override.
A backend service stores a collection of shape objects in C++. A developer writes the following code to compute the total area:
std::vector<Shape> shapes;
shapes.push_back(Circle(5.0));
shapes.push_back(Rectangle(3.0, 4.0));
for (Shape s : shapes) {
std::cout << s.area() << "\n";
}
Despite Circle and Rectangle overriding area(), every call prints the base Shape::area() result. What is the most likely cause?
3. What You Actually Need to Know
- Overriding vs. overloading are different. Overriding replaces a parent's method in a child (runtime dispatch). Overloading means multiple methods with the same name but different parameters in the same (compile-time; Python effectively doesn't have it).
- Use
@Override(Java) oroverride(C++11+). These make the compiler check you actually overrode something — catches typos likeequals()vs.equal()that silently create a new method. - C++: mark destructors
virtualin any class you might inherit from. Otherwisedelete parent_ptron a child object is undefined behavior — typically the child's destructor never runs and its resources leak. - C++: beware object slicing. Assigning a
Dogto anAnimal(by value) copies only theAnimalpart. Use pointers or references for . - Calling parent methods —
super().method()(Python),super.method()(Java),Parent::method()(C++). Common in__init__/ constructors so the parent initializes its own fields. - Debugging clues:
- "The parent's method ran when I expected the child's" — in C++, the method isn't
virtual, or you're calling by value (slicing). In Java, you declared itstaticorfinal. In Python, check your class hierarchy / MRO. NoSuchMethodErrorafter refactoring (Java) — a caller was compiled against an older class version; the method signature changed.- Infinite in
__init__(Python) — you typedself.__init__(...)instead ofsuper().__init__(...).
- "The parent's method ran when I expected the child's" — in C++, the method isn't
Abstract classes and interfaces
Sometimes the parent is never meant to be instantiated — it just defines a contract. That's an , and a method with no body is an abstract method.
# Python
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self): ...
# Shape() → TypeError: Can't instantiate abstract classC++ polymorphism uses pointers or references
Through an Animal pointer, the vtable runs Dog's virtual speak. A copy by value slices off the Dog part, so Animal's speak runs.
Variable a is an Animal pointer to a Dog, and Animal declares speak virtual.
Text version
- Variable a is an Animal pointer to a Dog, and Animal declares speak virtual.
- The call follows the object's hidden vtable pointer, so Dog's speak returns Rex says woof.
- Assigning a Dog to a plain Animal variable copies only the Animal part.
- On the copy, Animal's speak returns three dots, so use a pointer or reference for polymorphism.
A C++ backend service manages database connections through a ConnectionPool base class, with a PostgresPool subclass that allocates additional resources in its constructor. The service stores pool objects via ConnectionPool* pointers and calls delete on them at shutdown. A developer notices that PostgresPool's destructor never runs, causing resource leaks. What is the most likely cause?
4. Language Differences
| Aspect | Python | Java | C++ |
|---|---|---|---|
| Inherit syntax | class B(A): | class B extends A | class B : public A |
| Multiple inheritance | Yes — method lookup order is deterministic (C3 linearization algorithm) | No (only multiple interfaces) | Yes (diamond problem possible) |
| Call parent method | super().foo() | super.foo() | A::foo() |
| Default dispatch | Always dynamic | Always dynamic (unless final/static/private) | Static unless virtual |
| Override keyword | None (duck typed) | @Override (optional but recommended) | override (C++11+) |
| Prevent inheritance | Convention only | final class | final (C++11+) |
| Abstract class | ABC + @abstractmethod | abstract class | class with = 0 method |
| Interface | Protocols (3.8+) / duck typing | interface | Abstract class with only pure virtuals |
Python's MRO (Method Resolution Order) is worth a glance: D.__mro__ shows exactly which will be checked for a method, in order. For simple hierarchies it's obvious; for diamond it's the rule. Python computes MRO using an algorithm called C3 linearization — you rarely need the details, just know that __mro__ is the source of truth when you're unsure which method will win.
C++'s diamond problem — D inherits from both B and C, both of which inherit from A — is solved with virtual ( B : virtual public A), which ensures one shared A sub- rather than two.
A Python backend service has a class hierarchy where APIHandler inherits from both AuthMixin and LoggingMixin, and both mixins themselves inherit from a shared BaseHandler class. A developer wants to know which version of BaseHandler.setup() will be called when APIHandler invokes it. What is the most reliable way to determine this in Python?
5. Tradeoffs & Decisions
- vs. . couples child to parent forever — changes to the parent ripple into every descendant. (holding another as a field and delegating to it) is looser and easier to change. Default to composition; reach for inheritance when the relationship is genuinely "is-a" and you need .
- Deep hierarchies hurt. More than two or three levels of inheritance tends to be a warning sign. Each level adds indirection and hidden behavior. Flatten with composition.
- vs. (Java / C++). An declares a contract with no state. An can declare a contract and provide shared implementation. Choose interface when you just need the shape; abstract class when subclasses share real code.
virtualin C++ is a commitment. Addingvirtuallater to a you already released changes the 's memory layout (the vtable pointer), which forces every user of the to recompile — an "ABI break" (Application Binary Interface — the layout and calling conventions that compiled code depends on). Decide up front: is this class meant to be a polymorphic base?- Template Method vs. Strategy. Both enable "variation in one step of a fixed algorithm." Template Method uses inheritance (override the step). Strategy uses composition (inject an object with that step). Strategy is usually more flexible.
If you find yourself about to override a concrete parent method to subtly change its behavior, pause: you're probably making the parent's invariants harder to reason about. Consider holding the object instead (composition) or promoting the varying step to a strategy.
Choose how objects share behavior
Composition makes behavior easier to change; inheritance fits a genuine is-a relationship that needs polymorphism.
Inheritance extends a parent, while composition holds another object and delegates to it.
Text version
- Inheritance extends a parent, while composition holds another object and delegates to it.
- Changes to a parent ripple into descendants, while composition keeps the link looser.
- Pick inheritance only for a true is-a link that needs polymorphism, and composition for the rest.
- Default to composition because it is easier to change.
Your team maintains a published C++ library used by dozens of downstream services. You now want to allow subclasses to override a key method in one of the library's core classes, but that method is currently non-virtual. What is the primary risk of adding virtual to that method in a new release?
6. Interview Cheat Sheet
- lets a reuse and extend another; lets code written against the parent type work with any child type.
- Dynamic dispatch — the actual method called is chosen at runtime based on the 's real . It uses a vtable (C++/Java) or an MRO lookup (Python).
- Python and Java dispatch all instance methods dynamically by default. C++ requires
virtual; non-virtual calls are resolved at compile time. - Override vs. overload — override = same signature, different class (runtime); overload = same name, different parameters, same class (compile-time).
- Prefer over unless you have a genuine "is-a" relationship and need .
Follow-ups:
- "What's a vtable?" — A per-class table of function pointers. Each polymorphic holds a hidden pointer to its class's vtable; calling a virtual method is an indirect call through that table.
- "Why does C++ have
virtualbut Java doesn't?" — Java assumes dynamic dispatch everywhere (pay the cost by default); C++ lets you opt in, so non-virtual calls are as fast as regular function calls. - "What's object slicing?" — C++-specific: assigning a derived object to a base-type by value copies only the base part. Use pointers or references for polymorphism.
- "When would you use over inheritance?" — Almost always. Pick inheritance only when you truly have an "is-a" relationship and need the substitutability for polymorphism.