Diamond Problem — Core Concepts

What Is the Diamond Problem?

The diamond problem occurs when a class inherits from two classes that share a common ancestor. The inheritance graph forms a diamond shape:

    A
   / \
  B   C
   \ /
    D

Class D inherits from both B and C, which both inherit from A. The question is: when D calls a method defined in A (and possibly overridden by B and C), which version runs? Does A’s method execute once or twice?

Why It’s a Real Problem

In languages like C++, the diamond can cause genuine bugs:

  • Duplicate state: A.__init__ runs twice — once through B, once through C — creating duplicate copies of A’s attributes.
  • Ambiguous calls: If both B and C override A.method(), which override does D get?
  • Silent data corruption: Two initializations of the same base class can overwrite each other’s data.

How Python Solves It: The MRO

Python uses the Method Resolution Order (MRO), computed by the C3 linearization algorithm. It creates a single, linear order for method lookup that visits each class exactly once.

For the diamond above, Python’s MRO is: D → B → C → A → object.

What This Means in Practice

  • When D calls super().__init__(), it calls B.__init__.
  • B.__init__ calls super().__init__(), which calls C.__init__ (not A!).
  • C.__init__ calls super().__init__(), which finally calls A.__init__.
  • A.__init__ is called exactly once.

The crucial insight: super() doesn’t mean “my parent.” It means “the next class in the MRO.” This is what prevents the duplicate call to A.

Why Other Languages Handle It Differently

LanguageApproach
JavaForbids multiple class inheritance (interfaces only)
C++Allows it, uses virtual inheritance to opt into single-copy semantics
RubyUses mixins with a linearized module chain (similar to Python’s MRO)
ScalaUses linearized trait stacking
PythonC3 linearization with cooperative super()

Java’s approach is the most restrictive — you simply can’t create the diamond with classes. C++ gives you the tools but requires manual virtual annotations to avoid duplicate bases.

Checking the MRO

You can always inspect the order Python will use:

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

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

When the Diamond Appears Unintentionally

The diamond shows up more often than you’d think:

  • Every class inherits from object. So any multiple inheritance creates at least a mini-diamond with object at the top.
  • Framework mixins often inherit from a common base. Django’s View is the apex of many diamonds.
  • Standard library types like OrderedDict (which inherits from both dict and some mixin behaviors) have diamond patterns internally.

Common Misconception

“Multiple inheritance always causes the diamond problem.” Not true. The diamond only occurs when two parents share an ancestor. If your parents are completely independent classes, there’s no diamond and no ambiguity. The diamond is specifically about the shared ancestor.

Practical Guidelines

  1. Check the MRO when designing class hierarchies with multiple inheritance. Print YourClass.__mro__ to verify the order matches your expectations.
  2. Always call super() in methods you override — this is what makes the MRO work correctly.
  3. Keep it shallow. Deep diamonds (4+ levels) become hard to reason about. Consider composition if the hierarchy gets complex.
  4. Use mixins. The cleanest diamond patterns involve one concrete base class plus lightweight mixins that add specific behaviors.

One thing to remember: Python’s diamond problem solution is the MRO — a linear ordering that visits each ancestor exactly once, computed automatically at class definition time. Call super(), and the MRO handles the rest.

pythonadvancedoop

See Also