Method Resolution Order (MRO)
The MRO is the deterministic order — computed by C3 linearization — in which Python searches base classes for an attribute or method, and the exact order `super()` walks to make multiple inheritance cooperative.
Learning objectives
- › Explain what the MRO is and read a class's `__mro__`
- › Describe how C3 linearization orders bases and resolves the diamond problem
- › Use `super()` as 'the next class in the MRO', not 'the parent class'
- › Write cooperative `__init__` methods that forward `**kwargs` up the chain
Prerequisites
Version notes
- · Python 3 computes the MRO with C3 linearization for every class; Python 2 old-style classes used a simpler depth-first order that mishandled diamonds.
- · Zero-argument `super()` has worked since Python 3.0 by reading the compiler-injected `__class__` cell, so you rarely need `super(ClassName, self)`.
- · A metaclass may override `type.mro()` to customise resolution, but that is almost never needed in application code.
Simple explanation
What it is. The Method Resolution Order is the linear, deterministic sequence of classes Python searches when you access an attribute or method on an instance. For single inheritance it is simply child -> parent -> ... -> object, but with multiple inheritance Python computes the order with an algorithm called C3 linearization and stores it as `ClassName.__mro__`.
Why it exists. With multiple inheritance a name could come from several base classes. Python needs one unambiguous, predictable rule for which base wins, and that rule must respect the order you listed the bases in and must never place a class before one of its own subclasses. C3 linearization is exactly that rule.
The problem it solves. It answers 'which method actually runs?' and it makes `super()` well-defined. Cooperative multiple inheritance — mixins that each do their part and then call `super()` — only works because every class delegates to the *next* class in the MRO instead of to a hard-coded parent.
How it works. C3 merges the linearizations of all the base classes together with the direct base list, always preserving two constraints: children come before parents, and the left-to-right order of the bases is kept. `super()` does not mean 'my parent' — it means 'the next class after me in this instance's MRO', which is why `super().__init__()` can reach a sibling class you never explicitly inherited from.
Analogy: Think of the MRO as a relay-race running order, computed once and pinned to the wall. When you call `super()` you don't choose who runs next — you hand the baton to whoever's name sits directly below yours on that list. Cooperative `__init__` is every runner passing the baton along so every leg gets run exactly once.
Common misconceptions
- "`super()` calls the parent class." — It calls the *next class in the MRO*, which depends on the runtime type of self and may be a sibling, not the class written above you.
- "Diamond inheritance runs the shared base twice." — C3 places the common base exactly once, at the end, so cooperative `super()` invokes it a single time.
- "The MRO is depth-first." — Old Python 2 classes were; C3 is not. It keeps subclasses before parents and preserves base order, so D(B, C) linearizes to D, B, C, A, object — never D, B, A, C, A.
- "You can list bases in any order." — If the bases impose contradictory orderings, C3 fails and Python raises TypeError: Cannot create a consistent method resolution order.
Common alternatives
- Composition (has-a) instead of multiple inheritance to sidestep MRO reasoning entirely
- Small, single-purpose mixins so the linearization stays easy to predict
- Abstract base classes (`abc`) to share an interface rather than implementation
- Explicit delegation to a named base when you deliberately want to bypass the MRO
Code examples
Note: C3 keeps the left-to-right base order (B before C) and puts the shared base A exactly once, after both, with object last. Lookup for whoami walks D -> B and stops at B.
Behind the scenes
- ▸C3 linearization computes L[C] = C + merge(L[B1], ..., L[Bn], [B1, ..., Bn]); `merge` repeatedly takes the head of the first list that does not appear in the tail of any other list.
- ▸C3 guarantees two invariants: every class precedes its parents (monotonicity), and the left-to-right order in which you listed the bases is preserved.
- ▸`ClassName.__mro__` is a read-only tuple cached at class-creation time; `ClassName.mro()` returns the same order as a fresh list and can be overridden by a metaclass.
- ▸Instance attribute lookup walks `type(obj).__mro__` in order and returns the first class whose `__dict__` contains the name — which is exactly why the MRO decides 'which method wins'.
- ▸If `merge` gets stuck because the bases disagree on order, class creation fails with `TypeError: Cannot create a consistent method resolution order (MRO) for bases ...`.
Interview insights
Key definitions
- · Method Resolution Order (MRO): the linear order Python searches a class and its ancestors for an attribute, computed by C3 linearization and stored in `__mro__`.
- · C3 linearization: the algorithm that merges base-class orderings into one sequence that keeps every class before its parents and preserves the left-to-right base order.
- · Cooperative multiple inheritance: a pattern in which every class calls `super()` so each ancestor participates exactly once, in MRO order.
What is the MRO and how is it computed?
Concise answer: The Method Resolution Order is the fixed sequence Python searches when resolving an attribute or method on an instance. Python computes it with C3 linearization and stores it in ClassName.__mro__; lookup returns the first class in that order whose __dict__ has the name.
Detailed answer: Every class gets an MRO at creation time via C3 linearization, which merges the MROs of all bases with the base list while preserving two rules: a class always precedes its parents, and the left-to-right order of bases is kept. For class D(B, C) where B and C both extend A, the MRO is D, B, C, A, object — A appears once, at the end. Read it with D.__mro__ (a tuple) or D.mro() (a list). Lookup walks this order and stops at the first match.
Common mistake: Assuming lookup is depth-first (D, B, A, C) — C3 is not depth-first and never repeats A.
Follow-ups:
- · What two invariants does C3 guarantee?
- · When can C3 fail to produce an MRO?
Is `super()` the same as calling the parent class? Explain with a diamond.
Concise answer: No. super() delegates to the next class in the instance's MRO, not to the textual parent. In D(B, C), inside B a super().method() call reaches C (B's sibling) because C follows B in D's MRO — even though B does not inherit from C.
Detailed answer: super() binds to type(self).__mro__ and the current class, then dispatches to the next class after the current one in that order. This is what makes cooperative multiple inheritance work: in the diamond D(B, C) -> A, calling super().__init__() in each class walks D -> B -> C -> A -> object, so every __init__ runs exactly once. If instead each class hard-coded A.__init__(self), A would run twice (once via B, once via C). The takeaway: super() means 'next in the MRO', and the MRO depends on the concrete type of self, not on where the class sits in the source.
Common mistake: Reading super() as 'the base class above me' and hard-coding base calls, which double-invokes shared bases.
Follow-ups:
- · Why does cooperative __init__ forward **kwargs?
- · What happens if one class in the chain forgets to call super()?
When does Python raise 'Cannot create a consistent method resolution order (MRO)'?
Concise answer: When the bases impose contradictory orderings that C3 cannot merge — classically, listing a base before its own subclass, or combining two hierarchies that disagree on order. Fix it by reordering the bases or restructuring the hierarchy.
Detailed answer: C3's merge step repeatedly takes the head of the first candidate list that does not appear in the tail of any other. If no such head exists, the ordering is inconsistent and class creation fails with a TypeError. The classic trigger is a hierarchy that demands both 'A before B' and 'B before A' at once — for example class Bad(A, B) when B is already a subclass of A, so B must precede A but the base list asks for A first. The fix is to make the base ordering consistent, or to flatten the hierarchy, often by favouring composition or small single-purpose mixins.
Common mistake: Listing a base before its own subclass, e.g. class Bad(A, B) where B subclasses A.
Follow-ups:
- · How does class Bad(A, B) violate monotonicity?
- · How do small mixins usually avoid this?
Knowledge check
Given the classes below, what does `[c.__name__ for c in D.__mro__]` print?
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): passIn a diamond where every class uses cooperative `super().__init__()`, how many times does the shared base's `__init__` run?
Inside a method, `super()` delegates to:
Score: 0/3