Visual walkthrough — `super()` — calling parent methods
We start from absolute zero. Every word — class, method, override, MRO, proxy — is built before it is used.
Step 1 — What a "class" and a "method" even are
WHAT. Picture three boxes stacked in a family: Animal at the bottom (the grandparent), Dog sitting on top of it (the child). An arrow points from child up to parent — that arrow is called inheritance (see Inheritance — extending classes).
WHY. super() only makes sense once there is an above and a below. The arrow is the road super() will travel.
PICTURE. Two boxes, one arrow. The child box carries its own __init__; the parent box carries its __init__. Same name, two versions — that name-clash is the whole reason super() exists.

Step 2 — What "override" means, and the trap it sets
WHAT. Both boxes have a method named __init__. When you build a Dog, Python looks in Dog first, finds __init__ there, runs it, and stops looking.
WHY show this? Because "stops looking" is the danger. The parent's __init__ set self.legs = 4. If the child overrides and never reaches up, legs is never created — an `AttributeError` waiting to happen.
PICTURE. A search-beam enters Dog, hits __init__, and halts. The parent's __init__ sits in the dark, un-run. The red "STOP" is the bug super() fixes. (See also Method Overriding vs Overloading.)

Step 3 — The MRO is just a list, laid out flat
WHAT. For our simple pair:
Dog— the class of the object we made; search begins here.Animal— the parent; searched only ifDoglacks the name.object— the invisible ancestor every Python class ends at; it has a do-nothing__init__.
WHY a flat line and not a tree? Because a tree has forks, and a fork forces a choice ("which parent first?"). A line has no forks — the choice is already baked in. super() never decides; it just reads the next slot.
PICTURE. Three beads on a wire, an arrow pointing right labelled "search direction." This wire is the track our marble rolls along in every remaining step.

Step 4 — super() is a cursor pointing at "the next bead"
WHAT. The equivalence the parent note stated:
Dog— the class the code is written in (Python fills this in for you).self— the live object; the proxy keeps hold of it so you never pass it again.- The result: "find the bead after
Dogontype(self).__mro__."
WHY no self in super().__init__(name)? The proxy already clutches self. Hand it self a second time and Python complains: got multiple values for argument self. One marble, one rider.
PICTURE. A cursor sitting between Dog and Animal, arrow pointing at Animal. A small tag "self bound" rides on the cursor.

Step 5 — Watch one call: Dog("Rex") step by step
WHAT. Trace Worked Example 1 as motion along the wire:
Dog.__init__runs. It sayssuper().__init__(name)→ cursor jumps toAnimal.Animal.__init__runs: setsself.name = "Rex",self.legs = 4.- Control returns to
Dog.__init__, which now addsself.breed.
WHY parent first, then child? So the foundation (name, legs) exists before the child stacks its own floor (breed) on top. Extend, don't replace.
PICTURE. A marble rolls Dog → Animal, then returns; the state dictionary fills bottom-up: first name + legs appear (green), then breed (yellow).

Step 6 — The diamond: why a flat line beats the naive call
WHAT. C3 linearization (Method Resolution Order (MRO) & C3 Linearization) collapses the ◇ into one line:
Aappears once, at the far end — after bothBandC.
WHY does super() inside B go to C, not A? This is the surprise. super() never means "my parent." It means "the next bead on this line." In D's line, the bead after B is C, not A. So A is delayed until both siblings are done — hence run once.
PICTURE. The ◇ tree on the left, the flattened wire D→B→C→A→object on the right, with a dotted line showing B's super() skipping sideways to C.

Step 7 — The marble rolls the whole diamond: D B C A
WHAT. Run D() from Worked Example 3. Each __init__ prints its letter, then calls super():
DprintsD, cursor →BBprintsB, cursor →C(the sideways skip!)CprintsC, cursor →AAprintsA, cursor →object(does nothing)
Output: D B C A — each exactly once.
WHY not D B A C A? The naive A.__init__(self) inside both B and C would visit A twice. super() follows the single wire, so there is only one A bead to land on.
PICTURE. The marble's full path drawn as a numbered trail 1→2→3→4 across D B C A, with the phantom "double-A" path shown ghosted-out in red to mark what we avoided.

Step 8 — Degenerate & edge cases (so nothing surprises you)
PICTURE. Three mini-wires side by side: (a) broken run stopping at bead 1 with a red gap, (b) a two-bead single-inheritance wire, (c) the marble resting harmlessly on object.

The one-picture summary
Everything above compressed: the ◇ tree becomes one wire, the marble rolls left to right printing each letter once, and the state builds bottom-up. If you can read this single frame, you can predict any super() chain.

Recall Feynman: the whole walk in plain words
Imagine a relay race where the runners are lined up in one fixed order: D, then B, then C, then A, then a finish-line wall called object. When you shout "go" (D()), the first runner does his bit and, instead of naming the next runner, just says "next!" — that's super(). The word "next" always means the person immediately to the right on the line, so nobody runs twice and nobody is skipped. Even in a family shaped like a diamond, Python has already ironed the family tree into this single straight line, so "next" is never ambiguous. If a runner forgets to shout "next," the baton stops and the people down the line never set up their part — that's your missing self.legs. And you never hand the runner your own name (self) because the baton is already in his hand.
Connections
- Read this in Hinglish →
- Inheritance — extending classes
- Method Resolution Order (MRO) & C3 Linearization
- Method Overriding vs Overloading
- Multiple Inheritance & the Diamond Problem
- `__init__` — constructors
- Composition over Inheritance