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.

Quick check

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?

Choose one answer

2. How It Works

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 virtual are. 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
  1. Puppy inherits from Dog, which already inherits from Animal.
  2. The inherited initializer sets the name on this Puppy instance.
  3. Puppy overrides speak, so its version runs, and super calls Dog's speak.
  4. Dog reads the instance name and returns Rex says woof to Puppy.
  5. Puppy adds its small suffix to the message from Dog.
  6. The printed message combines parent behavior with the child override.
Quick check

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?

Choose one answer

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) or override (C++11+). These make the compiler check you actually overrode something — catches typos like equals() vs. equal() that silently create a new method.
  • C++: mark destructors virtual in any class you might inherit from. Otherwise delete parent_ptr on a child object is undefined behavior — typically the child's destructor never runs and its resources leak.
  • C++: beware object slicing. Assigning a Dog to an Animal (by value) copies only the Animal part. 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 it static or final. In Python, check your class hierarchy / MRO.
    • NoSuchMethodError after refactoring (Java) — a caller was compiled against an older class version; the method signature changed.
    • Infinite in __init__ (Python) — you typed self.__init__(...) instead of super().__init__(...).

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 class

C++ 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
  1. Variable a is an Animal pointer to a Dog, and Animal declares speak virtual.
  2. The call follows the object's hidden vtable pointer, so Dog's speak returns Rex says woof.
  3. Assigning a Dog to a plain Animal variable copies only the Animal part.
  4. On the copy, Animal's speak returns three dots, so use a pointer or reference for polymorphism.
Quick check

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?

Choose one answer

4. Language Differences

AspectPythonJavaC++
Inherit syntaxclass B(A):class B extends Aclass B : public A
Multiple inheritanceYes — method lookup order is deterministic (C3 linearization algorithm)No (only multiple interfaces)Yes (diamond problem possible)
Call parent methodsuper().foo()super.foo()A::foo()
Default dispatchAlways dynamicAlways dynamic (unless final/static/private)Static unless virtual
Override keywordNone (duck typed)@Override (optional but recommended)override (C++11+)
Prevent inheritanceConvention onlyfinal classfinal (C++11+)
Abstract classABC + @abstractmethodabstract classclass with = 0 method
InterfaceProtocols (3.8+) / duck typinginterfaceAbstract 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.

Quick check

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?

Choose one answer

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.
  • virtual in C++ is a commitment. Adding virtual later 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
  1. Inheritance extends a parent, while composition holds another object and delegates to it.
  2. Changes to a parent ripple into descendants, while composition keeps the link looser.
  3. Pick inheritance only for a true is-a link that needs polymorphism, and composition for the rest.
  4. Default to composition because it is easier to change.
Quick check

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?

Choose one answer

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 virtual but 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.

Reading

On this page

My notes

Sign in to keep highlights and notes.

Glossary history

Click dotted jargon to save explanations here.