What is `super()`, and how does it interact with the MRO in multiple inheritance?

6 minadvancedoopinheritancesupermixins

Quick Answer

`super()` returns a **proxy object** that resolves method calls against the **next class in the MRO after the current one**, not literally "my parent class." In single inheritance this looks identical to calling the parent directly; in multiple inheritance (cooperative mixins), it's what lets every class in the chain run its `__init__`/method in turn when each one calls `super().__init__()`.

Detailed Answer

super() is "next in MRO," not "my parent"

class LoggingMixin:
    def __init__(self, *a, **kw):
        print("LoggingMixin init")
        super().__init__(*a, **kw)

class TimestampMixin:
    def __init__(self, *a, **kw):
        print("TimestampMixin init")
        super().__init__(*a, **kw)

class Service(LoggingMixin, TimestampMixin):
    def __init__(self):
        print("Service init")
        super().__init__()

Service()
# Service init
# LoggingMixin init
# TimestampMixin init

Service.__mro__ is [Service, LoggingMixin, TimestampMixin, object]. Inside LoggingMixin.__init__, super() doesn't call object.__init__ directly — it calls whatever is next after LoggingMixin in Service's MRO, which happens to be TimestampMixin. This only works because every mixin cooperatively calls super().__init__() and accepts *args, **kwargs it doesn't recognize, passing them along.

Why "cooperative" multiple inheritance requires this discipline

If any mixin in the chain doesn't call super().__init__(), the chain breaks and classes later in the MRO never get initialized. This is why well-designed mixins always call super() even though, read in isolation, it's not obvious what "next" even refers to — it depends on the final class that combines them, which the mixin author can't know in advance.

Single inheritance: looks simple, same mechanism

class Animal:
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)   # calls Animal.__init__
        self.breed = breed

Here super() happens to resolve to Animal because the MRO is just [Dog, Animal, object] — but it's the same "next in MRO" lookup, not special-cased single-inheritance syntax.

super() vs calling the parent class explicitly

class Dog(Animal):
    def __init__(self, name, breed):
        Animal.__init__(self, name)   # works, but bypasses MRO
        self.breed = breed

Animal.__init__(self, name) calls Animal directly and skips the MRO entirely — in multiple inheritance this can call a class twice or skip classes, so super() is preferred whenever cooperative multiple inheritance (mixins) is in play.

Interview-ready summary: super() doesn't mean "my parent class" — it means "the next class after me in this instance's MRO." That indirection is precisely what allows mixins to be composed in any order and still all run, as long as every class in the chain forwards the call with super().__init__(*args, **kwargs).

Related Resources