InterviewsVector

How Inheritance Works in Python: super(), the MRO & Composition vs Inheritance

Quick answer

A class inherits by naming its parent in parentheses: class Dog(Animal). The subclass gets the parent's attributes and methods, can add its own, and can override them; call the parent version with super(). Python supports multiple inheritance and resolves method lookup through the MRO (method resolution order, computed by C3 linearisation), which you can inspect with ClassName.__mro__. For code reuse, senior engineers often favour composition over deep inheritance.

Short answer: A class inherits by naming its parent in parentheses — class Dog(Animal). The subclass gets the parent's attributes and methods, can add its own, and can override them; reach the parent version with super(). Python supports multiple inheritance, resolved through the MRO (Class.__mro__). For reuse, prefer composition over deep inheritance.

Inheritance questions get interesting fast: super() with multiple parents, the method resolution order, the diamond problem, and the senior judgement call of composition versus inheritance. Here's the depth interviewers actually probe.

Basic inheritance and super()

class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        raise NotImplementedError
 
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)      # run the parent's __init__ — don't reassign self.name by hand
        self.breed = breed
    def speak(self):                # override
        return "Woof!"
 
d = Dog("Rex", "Corgi")
print(d.name, d.breed, d.speak())   # Rex Corgi Woof!

super().__init__(name) delegates to the parent constructor. Skipping it (and setting self.name manually) works for single inheritance but breaks cooperative multiple inheritance — always prefer super().

Multiple inheritance and the MRO

Python allows multiple base classes and resolves lookups through the method resolution order, computed by the C3 linearisation algorithm:

class A:
    def who(self): return "A"
class B(A):
    def who(self): return "B"
class C(A):
    def who(self): return "C"
class D(B, C):
    pass
 
print(D().who())            # "B"
print([c.__name__ for c in D.__mro__])
# ['D', 'B', 'C', 'A', 'object']

D() resolves who to B because the MRO visits D → B → C → A → object. This is the diamond problem (D inherits from A twice via B and C), and the MRO is how Python resolves it consistently — each class appears once, children before parents.

Cooperative inheritance: super() follows the MRO

super() does not mean "my parent" — it means "the next class in the MRO." That's what makes a chain of super().__init__() calls each run exactly once:

class Base:
    def __init__(self): print("Base")
class Left(Base):
    def __init__(self): print("Left");  super().__init__()
class Right(Base):
    def __init__(self): print("Right"); super().__init__()
class Child(Left, Right):
    def __init__(self): print("Child"); super().__init__()
 
Child()      # Child, Left, Right, Base  — each runs once, via the MRO

If Left had called Base.__init__(self) directly instead of super(), Right.__init__ would be skipped. This is the single most common multiple-inheritance bug.

Mixins and abstract base classes

A mixin is a small class that adds focused behaviour via inheritance without being a full parent:

class JsonMixin:
    def to_json(self):
        import json; return json.dumps(self.__dict__)
 
class User(JsonMixin):
    def __init__(self, name): self.name = name
 
User("Ada").to_json()      # '{"name": "Ada"}'

An abstract base class goes the other way — it forces subclasses to implement methods:

from abc import ABC, abstractmethod
 
class Repository(ABC):
    @abstractmethod
    def get(self, id): ...
 
Repository()   # TypeError: can't instantiate abstract class

The senior take: composition over inheritance

Deep inheritance hierarchies are rigid — a change high up ripples everywhere, and a subclass is coupled to its parent's internals. Often you don't need "is-a", you need "has-a":

class Engine:
    def start(self): return "vroom"
 
class Car:
    def __init__(self):
        self.engine = Engine()      # composition: Car HAS an Engine
    def start(self):
        return self.engine.start()  # delegate

Composition is more flexible (swap the Engine at runtime), easier to test (inject a fake), and avoids the MRO gymnastics. "Favour composition over inheritance" is a phrase worth saying — and justifying — in a senior interview.

Common interview traps

  • Calling Parent.__init__(self) in multiple inheritance — skips siblings; use super().
  • Assuming super() means the literal parent — it's the next class in the MRO.
  • Deep hierarchies — the fragile-base-class problem; reach for composition.
  • Forgetting super().__init__() — the parent's state never initialises.

Interviewer follow-ups

  • "What's the diamond problem and how does Python solve it?" — multiple inheritance from a common ancestor; the MRO (C3) linearises it consistently.
  • "How does overriding relate to polymorphism?" — overriding is one way to achieve polymorphism; duck typing is the other.
  • "When would you NOT use inheritance?" — when the relationship is "has-a", or when you only want code reuse — use composition.

Sources

Key takeaways

  • Inherit by naming the parent: class Dog(Animal). Override methods freely; reach the parent version with super().
  • super() follows the MRO, not just 'the parent' — critical for correct cooperative multiple inheritance.
  • Python resolves multiple inheritance via the MRO (C3 linearisation); inspect it with Class.__mro__.
  • Mixins add focused behaviour via inheritance; abstract base classes force subclasses to implement methods.
  • Favour composition over deep inheritance: 'has-a' is more flexible and testable than a tall 'is-a' hierarchy.

Frequently asked questions

What does super() do in Python?

super() returns a proxy that delegates method calls to the next class in the MRO — not necessarily the literal parent. It is how a subclass calls the parent's implementation (commonly super().__init__(...)) and how cooperative multiple inheritance passes a call along the chain correctly.

What is the MRO in Python?

The Method Resolution Order is the linear sequence Python searches to find a method or attribute on a class and its ancestors. Python computes it with the C3 linearisation algorithm, which produces a consistent order even with multiple inheritance. Inspect it with ClassName.__mro__ or ClassName.mro().

Should I use inheritance or composition?

Use inheritance for a genuine 'is-a' relationship where the subclass is a specialised parent. Prefer composition ('has-a') when you just need to reuse behaviour — it is more flexible, avoids fragile deep hierarchies, and is easier to test and change. A common guideline is 'favour composition over inheritance'.

By Mohammad Wasi

Software Engineering Leader & Technical Author · Updated August 26, 2026


Related Posts