Visual walkthrough — Polymorphism — duck typing, same interface different behavior
This is the picture-by-picture companion to the parent topic.
Step 1 — An object is a labelled box of names
WHAT. Before any method call, we need to know what an object is to Python. It is a box (a region of memory) with labels (attribute names) pointing to things — data or functions. The box also carries one special label, __class__, pointing back to the recipe (the class) it was made from.
WHY. Every mystery about "which speak runs" is really a mystery about where Python looks for the label speak. If we do not first agree that names live in boxes, the search makes no sense.
PICTURE. Look at the blue box d. It has one everyday label, name → "Rex", and the hidden label __class__ → Dog. The word speak is not inside the box d itself — hold that thought, it is the whole story.

Step 2 — The class is a second box, sitting behind the object
WHAT. The method speak you wrote in class Dog: does not get copied into every Dog you make. It lives once, inside the Dog class box. Each instance box just remembers "my recipe is Dog" via __class__.
WHY. This is the key economy: a thousand dogs, one speak. So the object box is almost always missing the method — Python must be willing to look elsewhere. That "look elsewhere" is exactly where polymorphism will live.
PICTURE. Two boxes now. The small blue instance box d holds only name. The larger orange class box Dog holds speak. A gray arrow labelled __class__ runs from the instance up to the class. The label speak sits only in the orange box.

Step 3 — The dot is a search, not a fetch
WHAT. When Python evaluates d.speak, it runs a fixed procedure:
- Is
speaka label inside the instance boxd? If yes, use it. (Here: no.) - Else, follow
__class__to the class boxDog. Isspeakthere? If yes, use it. (Here: yes → stop.) - Else, keep climbing to base classes. If nothing has it → raise
AttributeError.
WHY. Notice what step 1–3 never ask: "Is d a Dog?" It only asks "does someone along this chain have a speak?" That single design choice is the entire mechanism of duck typing — Python checks capability, not pedigree.
PICTURE. The green arrow starts at the instance, finds nothing, hops to the class, and lands on speak. A red arrow shows the "not found" path that would end in AttributeError. Read the diagram as a flowchart of the dot.

Step 4 — Three unrelated classes, one identical call
WHAT. Now put three boxes side by side: Dog, Cat, Robot. Robot does not inherit from anything shared with Dog or Cat. Each class box holds its own speak returning a different string. We call the same line t.speak() on each.
WHY. This is the payoff of Step 3. Because the dot only searches for a label, three totally unrelated boxes are interchangeable as long as each has a speak. The caller make_it_speak(t) names no class — so it works for all three, and for a fourth you write later.
PICTURE. Three coloured class boxes, each with its own speak → "Woof" / "Meow" / "Beep boop". One shared call line t.speak() at the bottom fans out with three arrows, each landing in a different box and returning a different word.

Step 5 — The degenerate case: no speak anywhere
WHAT. Take a Fish whose class box has no speak. Run the exact same search: instance box empty, class box has no speak, no base class has it either. The chain is exhausted.
WHY. A derivation must cover every outcome, not just the happy path. We must know precisely what happens when the capability is absent, and where it happens — so that our error handling is deliberate, not accidental.
PICTURE. The green search arrow walks the full chain, finds nothing, and terminates in a red AttributeError box. Compare directly with Step 3's success path — same walk, different ending.

Step 6 — Overriding: two boxes with the same label, order decides
WHAT. Now let Dog inherit from Animal, where Animal has a speak and Dog also has a speak. The search list for a Dog instance is now [d, Dog, Animal, object]. Both Dog and Animal carry a speak, but the scan stops at the first one — Dog.
WHY. This explains override precisely, without hand-waving. Overriding is not "erasing the parent's method". Both methods still exist in their boxes; the child's just sits earlier in the search order, so it wins. The parent's speak is untouched and reachable via super().
PICTURE. A vertical stack Dog → Animal → object. Both Dog and Animal have a speak (orange). The green arrow scans top-down and halts at Dog's speak; a faded arrow shows Animal's speak still there but never reached. This ordered list is the MRO.

Step 7 — Same idea, hidden in syntax: + is a searched method too
WHAT. a + b is not a primitive; Python rewrites it to type(a).__add__(a, b) — a method search on a's class, identical in spirit to d.speak(). int, str, and list each keep their own __add__ in their class box.
WHY. This shows the mechanism is universal, not a special case for methods you happen to name yourself. Even the + symbol is duck-typed: 1+2, "a"+"b", [1]+[2] are one syntax dispatching to three different __add__.
PICTURE. The token + at the top fans into three class boxes (int, str, list), each with its own __add__, returning 3, "ab", [1, 2]. It is Step 4 wearing an operator's costume.

Recall Check yourself
Why does total_area([Circle(1), Square(2)]) equal ? ::: s.area() searches each shape's own class box: Circle.area returns , Square.area returns ; sum adds them.
Which term in type(x).__add__ makes 1+2 behave differently from "a"+"b"? ::: type(x) — the class searched changes, so a different __add__ box is found.
After class Dog(Animal) overrides speak, is Animal.speak gone? ::: No — it still lives in Animal's box; it is just later in the MRO, so Dog's wins. super().speak() still reaches it.
The one-picture summary
Everything above is a single sentence drawn out: x.name walks the MRO list [x, its class, its bases…] and stops at the first box that has name. Polymorphism, override, duck typing, and + are all just different chains being walked by the same walker.

Recall Feynman retelling of the whole walkthrough
Picture every object as a little box with sticky-note labels on it. Most boxes are almost empty — they just have a note saying "my recipe is over there," pointing at a bigger class box where the real instructions like speak are kept.
When you say thing.speak(), Python doesn't ask "what kind of thing is this?" It grabs a flashlight and walks a fixed line of boxes: first the object itself, then its class, then the class's parents, in a set order called the MRO. The moment the flashlight hits a box with a speak note, it stops and runs that one. If it walks off the end finding nothing, it shouts AttributeError.
That one habit explains everything. A Dog, a Cat, and a totally unrelated Robot all work with the same code because each of their boxes happens to have a speak note — the flashlight doesn't care about family, only about finding the note. Overriding just means the child's box sits earlier in the line, so its note gets found first while the parent's note quietly waits behind it. And even + plays the same game: it's secretly __add__, another note the flashlight goes hunting for. Same walker, different chains, different behavior — that is polymorphism.
Connections
- 2.1.12 Polymorphism — duck typing, same interface different behavior (Hinglish)
- Inheritance — overriding and super()
- Method Resolution Order (MRO)
- Abstraction — interfaces and abstract base classes (ABC)
- Dunder methods — __add__, __len__, __str__
- EAFP vs LBYL — Pythonic error handling
- Liskov Substitution Principle (SOLID)
- Encapsulation — bundling data and behavior