datarekha
Python Medium Asked at GoogleAsked at MetaAsked at Stripe

How does Python's Method Resolution Order (MRO) work, and what is the C3 linearization algorithm?

The short answer

Python resolves method lookups by computing a linearized list of classes called the MRO using the C3 linearization algorithm, which guarantees that a class always appears before its parents and that the local precedence order declared in each class definition is preserved. You can inspect it via `ClassName.__mro__` or `ClassName.mro()`.

How to think about it

MRO questions surface when an interviewer wants to probe multiple inheritance — the corner of Python that’s easiest to get subtly wrong. What they’re really after is whether you can explain why C3 exists (the naive search breaks the diamond) and read a class’s MRO, rather than recite an algorithm.

The problem C3 solves

Before C3, Python searched depth-first, left to right. Picture the diamond — A at the top, B and C beneath it, D inheriting from both:

    A
   / \
  B   C
   \ /
    D

Depth-first would walk D → B → A → C, reaching A before C. So if A defined a method and C overrode it, C’s override would never be seen — A would shadow its own descendant. C3 fixes this by pushing every base class behind its subclasses, so all of D’s immediate parents are checked before their shared ancestor.

C3 builds the order under two rules: a class always precedes its parents, and the left-to-right order of bases in each class statement is preserved. For class D(B, C) where both extend A, the result is [D, B, C, A, object].

A worked example

class A:
    def greet(self): return "A"
class B(A):
    def greet(self): return "B"
class C(A):
    def greet(self): return "C"
class D(B, C):
    pass                                   # no override of its own

print("D MRO:", [c.__name__ for c in D.__mro__])
print("D().greet():", D().greet())          # B wins — it precedes C

# Cooperative version: each greet calls the NEXT class in the MRO
class B2(A):
    def greet(self): return "B+" + super().greet()   # next after B2 is C2
class C2(A):
    def greet(self): return "C+" + super().greet()   # next after C2 is A
class D2(B2, C2):
    pass

print("D2 MRO:", [c.__name__ for c in D2.__mro__])
print("D2().greet():", D2().greet())        # B+C+A — the whole chain runs once
D MRO: ['D', 'B', 'C', 'A', 'object']
D().greet(): B
D2 MRO: ['D2', 'B2', 'C2', 'A', 'object']
D2().greet(): B+C+A

The second result is the one that rewires intuition. D2().greet() returns "B+C+A"B2’s super().greet() didn’t jump to A, it went to C2, the next entry in D2’s MRO, even though B2 was written knowing nothing about C2. That’s cooperative inheritance: each class hands off to whatever comes next in the line, and the shared base runs exactly once.

super() follows the MRO, not the parent

This is the whole point, worth stating plainly: super() does not call “the parent class.” It calls the next class in the linearized MRO — which, under multiple inheritance, may be a sibling you never named. That’s exactly why every class in a cooperative hierarchy has to call super(): drop one link and the rest of the chain is silently skipped.

Learn it properly Inheritance vs Composition

Keep practising

All Python questions

Explore further

Skip to content