Visual walkthrough — Method overriding — when and why
We are deriving the central result of Method overriding — when and why: the version that runs is chosen by the actual object's type, not the variable's type. Let's earn every piece of that sentence.
Step 1 — What a "call" even is
WHAT: We picture the object x as a little box in memory, and the method m as a house somewhere else. The call is an arrow: "go run that house."
WHY: Before we can ask which area runs, we must agree that running a method = jumping to a code address. Everything below is about how the arrow finds the right house.
PICTURE: The blue box on the left is the object. The magenta arrow is the call. Right now it points at one house — but soon there will be two houses named area, and we must choose.

Step 2 — Two classes, two houses named area
WHAT: We draw two classes. Shape has a method area (an empty promise — in Python it raises NotImplementedError). Circle inherits from Shape (see Inheritance) but writes its own area that computes .
WHY: This is the overload-vs-override fork, so let's pin down both words right now:
Here area has the same name and the same parameters (just self) in both classes → this is overriding. So now two houses answer to the name area, and the machine must pick.
- — the base definition, address of the "empty promise" code.
- — the override, address of the real code.
PICTURE: Two orange houses, both labelled area, one per class. The arrow of a call must land on exactly one.

Step 3 — Give each class a lookup table (the vtable)
WHAT: We build one table per class.
Reading term by term:
- — the key, the method name you typed.
- — "points to the code at".
- — the value, the actual address to jump to for a Circle.
WHY a table? Because the machine needs a fast answer to "which area?" without any if/else.
How Circle's table relates to Shape's: conceptually the child's table starts from the parent's entries, then patches the ones it overrides (here area). In reality most implementations build one table per class once, at class-load time — they don't literally duplicate the parent's array on every call; they lay out a fresh per-class array that shares the slot numbering and writes the child's addresses into the overridden slots. Either way the effect is identical: overridden entries point at the child's code, un-overridden entries still point at the parent's.
PICTURE: Two stacked tables. Circle's row for area is highlighted magenta to show it was patched; the faded grey arrow shows what it would point to if it hadn't overridden.

Step 4 — Every object carries a secret pointer to its class
WHAT: When you write c = Circle(2), memory holds a box with two things:
- the data (
r = 2), - a hidden
__class__pointer →vtable(Circle).
WHY: This hidden pointer is the entire secret of dynamic dispatch. No matter what variable holds the object, the object drags its own class-pointer along. The variable's label can lie; the object's inner pointer cannot.
PICTURE: The object box now has a dark navy compartment labelled __class__ with an arrow shooting to vtable(Circle). Follow that arrow — it never lies about what the object really is.

Step 5 — The trick: put a Circle in a Shape-typed box
WHAT: In Java-flavoured pseudocode Shape s = new Circle(2); (recall the cross-language note above — Python has no separate declared type, so we borrow Java here to make the split visible). Two type-labels exist:
- Declared type of
s:Shape(what the variable promises). - Runtime type of the object:
Circle(what the object is).
WHY show this? Because the naive guess is "the variable says Shape, so Shape.area runs." We are about to prove that guess wrong, and see why.
PICTURE: One arrow from the variable label s : Shape (a promise) and a different arrow from the object's own __class__ (the truth). They point at different vtables. Hold this tension — Step 6 resolves it.

Step 6 — Trace the dispatch: which house wins
WHAT — the three-hop walk:
- Read the object's hidden
__class__pointer → it saysCircle. (NotShape! The variable label is ignored here.) - Go to that class's vtable →
vtable(Circle). - Look up
area→ the entry we patched in Step 3 → jump toCircle.area.
Term by term in the rule:
- — the runtime class, read from the object's secret pointer (Step 4). Here .
- — that class's table (Step 3).
- — the lookup (array-index by slot in compiled languages; average- name lookup in dynamic ones).
- The result is → so runs. Child wins.
WHY it's correct: step 1 used the object's pointer, not the variable's label. That is the whole reason the declared type is powerless over execution. This is exactly what powers Polymorphism: one call site, many correct behaviours.
PICTURE: The full path traced in magenta: s → object → __class__ → vtable(Circle) → area → Circle.area. The greyed-out path to Shape.area shows the road not taken.

Step 7 — Edge case: the method is not overridden
WHAT: Suppose Triangle(Shape) defines no area. Its vtable's area slot still holds Shape.area (nothing patched it), so:
WHY: No override = the inherited entry survives = the parent's code runs. Same three-hop rule, no special case in the machine — the fallback is automatic because Step 3 only patches the slots a class actually overrides.
PICTURE: Triangle's table area row points (unhighlighted) straight back up to Shape.area. The dispatch arrow calmly lands on the parent house.

Step 8 — Degenerate case: super() — running both houses
WHAT: In super(), SavingsAccount.describe calls super().describe(). That call does not use vtable(SavingsAccount) for describe — it explicitly targets the parent's describe, runs it, then the child appends its extra text.
WHY: This is the "extend" flavour of overriding (vs the "specialize" flavour of Steps 1–6). We reuse instead of duplicate — keeping the code DRY and honouring Liskov (we only add, never narrow).
PICTURE: Two houses light up in sequence: first Account.describe (via the up-arrow), then control returns and SavingsAccount.describe finishes the sentence. Numbered 1→2 to show ordering.

Step 9 — Edge case: more than one parent (multiple inheritance / interfaces)
WHAT: Two situations break the simple model:
- Interface dispatch (Java/C#). A class implements many interfaces; each interface has its own slot layout. The object needs to find "this interface's
areaslot," so the runtime keeps an interface method table (itable) per implemented interface, and dispatch first picks the right itable, then the slot. - Multiple concrete parents (C++/Python). If two parents both define
area, the language needs a rule to break the tie. Python uses the MRO (Method Resolution Order) — a single, deterministic linear ordering of all ancestors (C3 linearisation); the lookup walks that order and takes the firstareait finds. C++ instead gives the object several vtable pointers (one per base) and may require you to disambiguate explicitly.
WHY it matters: the rule — "runtime type decides" — still holds, but the mechanism gains a layer: pick the correct table (by interface or by MRO position) before the slot lookup. Overriding still means "child's entry wins"; multiple inheritance only changes how the machine locates which entry counts as the child's.
PICTURE: One object with two class-pointers (to A and B), plus a linearised MRO list [Child, A, B, Base]; the dispatch arrow walks the list left-to-right and stops at the first house named area.

The one-picture summary
Everything compressed: two classes, two vtables, one object holding its own class-pointer, one call, and the magenta path that ignores the declared Shape label and lands on Circle.area.

Recall Feynman: the walkthrough in plain words
Imagine every object secretly wears a name-badge saying what kind of thing it really is. When you ask it to do a job like "tell me your area," the computer doesn't read the label on the box you're carrying it in — it reads the object's own badge. That badge points to a little address book (the vtable) belonging to its true class. It flips to the page for "area" and dials whatever number is written there. Because a Circle badge points to Circle's address book, and that book's "area" page was rewritten to say , the Circle's own answer runs — every single time, automatically, with no if/else. If a class never rewrote its "area" page, the page still holds the parent's number, so the parent's version runs instead. And super() is you deliberately flipping to the parent's address book for one call, so you can do their work first and then add your own. When a thing has two parents, it carries two badges (or one carefully-ordered list of ancestors) and the computer just checks them in a fixed order until it finds the page.
Recall
Which hop reads the declared (variable) type? ::: None — hops 1,2,3 all use the object's runtime class; the declared type only gates compilation.
Why does a non-overridden method still work? ::: The child's vtable slot still holds the parent's address, so the parent's code runs.
What makes super() different from a normal dispatch? ::: It deliberately targets the parent's method, skipping the child's patched vtable entry.
How does dispatch change under multiple inheritance? ::: The runtime first picks the right table (by interface/itable or by MRO position), then does the slot lookup; "runtime type decides" still holds.