Skip to content

Method Resolution Order#

Method Resolution Order (MRO) is the deterministic sequence Python uses to find methods and attributes in classes — especially critical with multiple inheritance. Python uses the C3 linearization algorithm, which guarantees each class appears once, respects inheritance order, and stays monotonic. Misunderstanding MRO causes subtle bugs with super() and is a favorite interview topic.

This page completes the MRO preview in Introdicion to OOPs and Inheritance and Multiple Inheritance.

How to use this page

Read after inheritance basics. Work through the diamond examples with Class.__mro__ in a REPL. Related: Polymorphism, Abstract Base Classes.

At a glance
Track Python Advanced → Object Oriented Programming
Sections 14 major topics
Outline Use the right-hand TOC to jump

Topics: Why MRO exists · Inspecting MRO · C3 linearization — intuitive rules · super() follows MRO, not the parent · super() with explicit arguments · Attribute lookup vs method lookup · Cooperative super() in __init__ · Mixins and MRO ordering · … (+6 more)

  1. Why MRO exists
  2. Inspecting MRO
  3. C3 linearization — intuitive rules
  4. super() follows MRO, not the parent
  5. super() with explicit arguments
  6. Attribute lookup vs method lookup
  7. Cooperative super() in __init__
  8. Mixins and MRO ordering
  9. MRO with ABCs
  10. Comparison with other languages
  11. Debugging MRO issues
  12. mro() dispatch visualization … and 2 more sections

Why MRO exists#

Without a strict order, multiple inheritance is ambiguous:

class A:
    def method(self): return "A"

class B(A):
    def method(self): return "B"

class C(A):
    def method(self): return "C"

class D(B, C):
    pass

D().method()   # Which method? → B's, per MRO

Python must answer: which method does D().method() invoke? MRO provides a single linear order.


Inspecting MRO#

class D(B, C):
    pass

D.__mro__
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

D.mro()        # same as list(D.__mro__)
help(D)        # shows MRO at top
Tool Output
Class.__mro__ Tuple of classes in lookup order
Class.mro() List version
help(Class) Human-readable hierarchy

Lookup rule: when accessing obj.method, Python searches type(obj).__mro__ in order, returning the first matching attribute.


C3 linearization — intuitive rules#

For class D(B, C):

  1. D comes first
  2. B before C (left-to-right in class statement)
  3. A (common ancestor) after both B and C
  4. object last
  5. No class appears twice
  6. If B comes before C in D's bases, B precedes C in MRO

Result: D → B → C → A → object

Merge intuition (formal C3)#

L(D) = D + merge(L(B), L(C), [B, C])
     = D + merge([B, A, object], [C, A, object], [B, C])
     = D + B + merge([A, object], [C, A, object], [C])
     = D + B + C + merge([A, object], [A, object])
     = D + B + C + A + object

You rarely compute this by hand — use Class.__mro__. Know that invalid hierarchies raise TypeError at class creation:

class X(A, B): ...   # OK
class Y(B, A): ...   # TypeError if inconsistent — can't create class

super() follows MRO, not the parent#

This is the most important MRO insight:

class A:
    def method(self):
        print("A")

class B(A):
    def method(self):
        print("B")
        super().method()

class C(A):
    def method(self):
        print("C")
        super().method()

class D(B, C):
    def method(self):
        print("D")
        super().method()

D().method()

Output:

D
B
C
A

super().method() in B calls C.method, not A.method directly — because C is next in D.__mro__ after B.

D.__mro__   # D, B, C, A, object

super() with explicit arguments#

Two-argument form (useful outside methods):

super(B, self).method()   # start search after B in MRO

Inside a method, zero-argument super() (Python 3) is equivalent to:

super().__thisclass__   # implicit in 3.x: super() in method
# CPython binds __class__ cell — super() uses current class and self

In Python 3, always prefer bare super() inside instance methods.

Classmethod super()#

class Base:
    @classmethod
    def setup(cls):
        return [cls.__name__]

class Derived(Base):
    @classmethod
    def setup(cls):
        return super().setup() + ["extended"]

Attribute lookup vs method lookup#

Same MRO applies to attributes:

class A:
    x = "A"

class B(A):
    x = "B"

class C(A):
    x = "C"

class D(B, C):
    pass

D.x          # "B" — first in MRO
D().x        # "B"

Instance attributes shadow class attributes regardless of MRO:

d = D()
d.x = "instance"
d.x   # "instance"

Cooperative super() in __init__#

Multiple inheritance requires every class to call super().__init__():

class Root:
    def __init__(self):
        print("Root")

class Left(Root):
    def __init__(self):
        super().__init__()
        print("Left")

class Right(Root):
    def __init__(self):
        super().__init__()
        print("Right")

class Combined(Left, Right):
    def __init__(self):
        super().__init__()
        print("Combined")

Combined()

Output (MRO: Combined → Left → Right → Root → object):

Root
Right
Left
Combined

Each super().__init__() invokes the next __init__ in MRO — Root runs once despite two parents.


Mixins and MRO ordering#

class LogMixin:
    def log(self, msg: str):
        print(f"[LOG] {msg}")

class CacheMixin:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._cache = {}

class Service(LogMixin, CacheMixin):
    def __init__(self, name: str):
        super().__init__()
        self.name = name

Service.__mro__
# Service, LogMixin, CacheMixin, object

Convention: list mixins left-to-right, most specific first, base class last:

class MyModel(JSONMixin, TimestampMixin, BaseModel):
    ...

MRO with ABCs#

ABCs participate like any class:

from abc import ABC, abstractmethod

class Readable(ABC):
    @abstractmethod
    def read(self) -> str: ...

class Writable(ABC):
    @abstractmethod
    def write(self, data: str) -> None: ...

class File(Readable, Writable):
    def read(self) -> str:
        return "data"

    def write(self, data: str) -> None:
        print(data)

File.__mro__   # File, Readable, Writable, ABC, object

Comparison with other languages#

Language MRO
Python 3 C3 linearization
Python 2 (old-style) Depth-first, left-to-right (problematic)
C++ Virtual inheritance (complex, manual)
Java Single inheritance only (interfaces separate)

Python 3's C3 guarantees:

  • Consistency — no ambiguous order
  • Monotonicity — subclasses preserve parent order
  • Local precedence — base order in class statement respected

Debugging MRO issues#

Symptom: super() skips expected class#

class A:
    def f(self): print("A")

class B(A):
    def f(self):
        print("B")
        super().f()

class C(A):
    def f(self): print("C")   # no super() — chain stops

class D(B, C):
    def f(self):
        print("D")
        super().f()

D().f()   # D, B, C — never reaches A because C doesn't call super()

Fix: every class in cooperative chain must call super().

Symptom: TypeError: Cannot create a consistent method resolution order#

Occurs when designing incompatible base orders — restructure inheritance.


mro() dispatch visualization#

flowchart LR
    D --> B
    D --> C
    B --> A
    C --> A
    A --> O[object]

Lookup for D().method(): visit D → B → C → A → object.


Practical interview examples#

Which class's method runs?#

class X:
    def who(self): return "X"

class Y(X):
    def who(self): return "Y"

class Z(X):
    def who(self): return "Z"

class W(Y, Z):
    pass

W().who()        # "Y"
W.__mro__        # W, Y, Z, X, object

super() return value chain#

class A:
    def compute(self) -> int:
        return 1

class B(A):
    def compute(self) -> int:
        return super().compute() + 10

class C(A):
    def compute(self) -> int:
        return super().compute() + 100

class D(B, C):
    def compute(self) -> int:
        return super().compute() + 1000

D().compute()   # 1111 = 1000 + 110 + 1? Let's trace:
# D.compute: super() → B.compute → super() → C.compute → super() → A.compute
# = 1000 + (10 + (100 + 1)) = 1111

MRO and __slots__#

Subclasses must declare parent slots:

class Base:
    __slots__ = ("x",)

class Child(Base):
    __slots__ = ("y",)   # implicit: ("x", "y") effective

Child.__slots__   # ("y",) only in __slots__ attr, but x works via Base

MRO affects which __dict__/__slots__ layout applies — advanced topic; know slots exist in inheritance.


Interview traps (quick reference)#

Trap What goes wrong Safe approach
Assuming super() = parent class Skips or wrong order in MI Think "next in MRO"
Broken cooperative chain Some classes never initialized Every class calls super()
Wrong mixin order Unexpected method wins Mixins first, base last
Hardcoding Parent.method(self) Bypasses MRO Use super()
Computing MRO by hand incorrectly Wrong interview answer Use Class.__mro__
Class without super() in diamond Parent method runs multiple times or not at all Cooperative pattern

Mental model checklist#

  1. How do you inspect MRO for any class?
  2. What does super().method() call in a diamond hierarchy?
  3. Why must every class in a cooperative chain call super()?
  4. What causes TypeError: Cannot create a consistent method resolution order?
  5. How does left-to-right base order in class D(B, C) affect MRO?

What's next#

Topic Page
Inheritance patterns Inheritance and Multiple Inheritance
Polymorphism Polymorphism
Class methods + MRO Class Methods and Static Methods
Basics OOP Introdicion to OOPs