datarekha
Python Medium Asked at GoogleAsked at MetaAsked at Stripe

How does `super()` work in Python, and why is the zero-argument form preferred over `super(ClassName, self)`?

The short answer

`super()` returns a proxy that delegates method calls to the next class in the MRO, not necessarily the direct parent. The zero-argument form `super()` (Python 3) is preferred because it uses a compiler-injected `__class__` cell, which correctly tracks the defining class even under multiple inheritance — avoiding the brittle repetition of the class name.

How to think about it

The interviewer is checking whether you know that super() is not a synonym for “call the parent.” It’s about the Method Resolution Order — the linearized list of classes Python searches for a method. In single inheritance the distinction is invisible; under a diamond-shaped hierarchy it’s everything.

Start with the easy case, because it builds the intuition. In single inheritance super().__init__() really does just call the parent:

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

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

Everyone reads that correctly. The interesting case is multiple inheritance, where “the parent” stops being well-defined.

A worked example

When D inherits from B and C, and both extend A, Python must run A.setup exactly once. The MRO is [D, B, C, A, object], and each class’s super() steps to the next entry — not to its own parent:

class A:
    def setup(self):
        print("A.setup")

class B(A):
    def setup(self):
        print("B.setup"); super().setup()   # next after B is C, not A

class C(A):
    def setup(self):
        print("C.setup"); super().setup()   # next after C is A

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

print("MRO:", [c.__name__ for c in D.__mro__])
print()
D().setup()
MRO: ['D', 'B', 'C', 'A', 'object']

D.setup
B.setup
C.setup
A.setup

Follow the chain: D calls super(), which lands on B; B’s super() lands on Cnot A, even though C is nowhere in B’s own bases; C’s super() finally reaches A. Each class runs once, in MRO order. Take the super() calls out of B and C and you’d skip classes or run A.setup twice.

Why the zero-argument form

super().__init__(name)            # Python 3 — preferred
super(Dog, self).__init__(name)   # explicit — error-prone

The explicit form hardcodes the class name. Rename Dog, copy the method into a subclass, or reshuffle the hierarchy, and that frozen name breaks — sometimes silently, sometimes as a baffling runtime TypeError. The bare super() instead reads __class__, a read-only cell the compiler injects at method-definition time; it always means “the class that textually defines this method,” no matter what self happens to be at runtime.

Learn it properly Inheritance vs Composition

Keep practising

All Python questions

Explore further

Skip to content