The super() function is one of Python's most powerful yet misunderstood features. Available since Python 2.2, it lets you call methods from parent classes elegantly, especially in multiple inheritance scenarios. If you've ever written super().__init__() inside a class, you have used super() — but you have probably not explored its full potential.

In this complete guide, you will understand what super() does, how it solves the Method Resolution Order (MRO) problem, how to use it in single and multiple inheritance, and which pitfalls to avoid. Let's dive deep with practical examples you can test in your own environment.

The Problem super() Solves

Before super() existed, Python developers had to call parent class methods explicitly by name. This worked well for single inheritance but created serious issues with multiple inheritance, especially the diamond problem — where a class inherits from two classes sharing a common ancestor.

# The problem: explicit class name calls
class Parent:
    def method(self):
        print("Parent.method called")

class Child(Parent): def method(self): Parent.method(self) # Explicit call print("Child.method called")

This approach has three major drawbacks:

  • Tight coupling: you must know the exact parent class name
  • Hard maintenance: changing the hierarchy requires updating every explicit call
  • Broken multiple inheritance: explicit calls bypass the MRO, skipping important classes in the chain

The super() function solves all these problems at once by providing a dynamic, hierarchy-aware way to delegate calls to ancestor methods.

Basic Syntax and How It Works

The most common way to use super() is without arguments inside an instance method:

class Parent:
    def method(self):
        print("Parent.method executed")

class Child(Parent): def method(self): super().method() # Delegates to Parent.method print("Child.method executed")

c = Child() c.method()

Output:

Parent.method executed

Child.method executed

When you call super() without arguments, Python automatically infers the current class and instance. This only works inside instance methods that receive self as the first parameter. The equivalent explicit form would be super(Child, self).method().

The object returned by super() is not the parent class itself. Instead, it is a proxy that knows how to traverse the Method Resolution Order (MRO) to find the next method in the inheritance chain.

super() in __init__

The most common real-world use of super() is inside the __init__ method to initialize the parent class portion of an object. This pattern is essential for building clean, reusable class hierarchies. To better understand how __init__ fits into Python's object model, check out our guide on Python magic methods. The official __init__ documentation also explains how this method is invoked during object creation.

class Animal:
    def __init__(self, name):
        self.name = name
        print(f"Animal.__init__: {self.name}")

class Dog(Animal): def init(self, name, breed): super().init(name) # Calls Animal.init self.breed = breed print(f"Dog.init: {self.name}, {self.breed}")

rex = Dog("Rex", "German Shepherd")

Output:

Animal.init: Rex

Dog.init: Rex, German Shepherd

Notice that we only pass the arguments the parent class needs. The super() function takes care of calling the correct __init__ up the hierarchy. This keeps each class responsible for its own initialization, following the single responsibility principle.

Understanding the MRO (Method Resolution Order)

The MRO is the algorithm Python uses to determine which base class method to call when you invoke a method on an instance. In Python 3, the MRO is based on the C3 Linearization Algorithm, which guarantees three important properties:

  • Local consistency: a class always comes before its subclasses
  • Order preservation: the order of base classes in the class definition is respected
  • Monotonicity: the order does not change when new classes are added

You can inspect the MRO of any class using the __mro__ attribute or the mro() method:

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

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

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

class D(B, C): pass

print(D.mro)

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

d = D() d.method() # "B" — follows the MRO

In the example above, the MRO for D is: D → B → C → A → object. This means that when super() is called inside B, Python will go to C, not directly to A. This is what makes cooperative multiple inheritance possible. Check the official documentation on inheritance for more context.

Cooperative Multiple Inheritance with super()

The true power of super() shines in multiple inheritance scenarios. When all classes in the hierarchy consistently use super(), Python can orchestrate each method call in the correct order, even in complex hierarchies. This pattern is known as cooperative multiple inheritance or chain of delegation.

class Worker:
    def __init__(self, name):
        self.name = name
        print(f"Worker.__init__({self.name})")

class Manager(Worker): def init(self, name, department): print(f"Manager.init started for {name}") super().init(name) self.department = department print(f"Manager.init done: {self.department}")

class Programmer(Worker): def init(self, name, language): print(f"Programmer.init started for {name}") super().init(name) self.language = language print(f"Programmer.init done: {self.language}")

class TechLead(Manager, Programmer): def init(self, name, department, language): print(f"TechLead.init started for {name}") super().init(name, department, language) print("TechLead.init done")

Testing

tl = TechLead("Anna", "Engineering", "Python")

The MRO ensures Worker.init is called only ONCE

print(TechLead.mro)

Without cooperative super(), Worker.__init__ would be called twice, causing duplicated initialization and potential side effects. The MRO ensures each __init__ in the chain executes exactly once. This pattern is explained in depth by Raymond Hettinger in "Python's super() considered super!", a must-read for anyone working with complex hierarchies.

Consistent Signatures (The *args, **kwargs Pattern)

One of the biggest challenges in cooperative multiple inheritance is handling different parameter sets. The classic solution is to use *args and **kwargs to accept any combination of arguments and forward them through super():

class Vehicle:
    def __init__(self, **kwargs):
        self.vehicle_type = kwargs.get('vehicle_type', 'generic')
        print(f"Vehicle.__init__: type={self.vehicle_type}")

class Motorized: def init(self, **kwargs): self.fuel = kwargs.get('fuel', 'gasoline') print(f"Motorized.init: fuel={self.fuel}")

class Car(Motorized, Vehicle): def init(self, kwargs): self.brand = kwargs.get('brand', 'unknown') self.model = kwargs.get('model', 'unknown') print(f"Car.init: brand={self.brand}, model={self.model}") super().init(kwargs)

my_car = Car(brand="Toyota", model="Corolla", vehicle_type="sedan", fuel="hybrid")

This cooperative super() call pattern is widely used in Python frameworks like Django, SQLAlchemy, and Flask. Each class extracts the arguments it cares about and passes the rest further up the chain.

The Explicit Form: super(Type, Object)

While the no-argument form is most common, super() also accepts explicit arguments: super(Type, object). This is useful in advanced scenarios such as class methods (@classmethod):

class Worker:
    @classmethod
    def create(cls, name):
        print(f"Worker.create called for {name}")
        return cls(name=name)

class Manager(Worker): @classmethod def create(cls, name, department="General"): print(f"Manager.create called for {name}")

Inside @classmethod, super() needs cls

    instance = super(Manager, cls).create(name=name)
    instance.department = department
    return instance

m = Manager.create("Carlos", "IT") print(f"{m.name}, {m.department}")

Inside class methods, super() without arguments also works in Python 3, but the explicit form makes the intent clearer. For static methods (@staticmethod), super() cannot be used since there is no class or instance reference.

Common Pitfalls and How to Avoid Them

1. Forgetting to Call super().__init__()

If you define __init__ in a child class without calling super().__init__(), the parent class will not be initialized. This is one of the most common bugs:

class Parent:
    def __init__(self):
        self.value = 42

class Child(Parent): def init(self): pass # Forgot to call super().init()

c = Child() print(c.value) # AttributeError: 'Child' object has no attribute 'value'

2. Base Class Order in Class Definition

The order in which you list base classes determines the MRO. Getting the order wrong can lead to unexpected behavior:

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

class B: def action(self): print("B")

class C(A, B): # A takes priority pass

class D(B, A): # B takes priority pass

C().action() # "A" D().action() # "B"

3. Breaking the super() Chain

If any class in the hierarchy does not call super(), the chain is broken and no class after it will execute:

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

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

Did not call super().method() — chain broken

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

c = C() c.method()

Only "B" and "C" are printed — "A" is never reached

Real-World Use Cases

Mixins and Composition

Mixins are small classes that add specific functionality. Combined with super(), they let you compose complex behaviors in a modular way:

class JSONMixin:
    def to_json(self):
        import json
        return json.dumps(self.__dict__)

class LoggerMixin: def log(self, message): print(f"[LOG] {message}")

class User(LoggerMixin, JSONMixin): def init(self, name, email): self.name = name self.email = email self.log(f"User {name} created")

u = User("Anna", "[email protected]") print(u.to_json())

Frameworks and ORMs

Frameworks like Django and SQLAlchemy use super() extensively in their class hierarchies. When you define a Django model and call super().save(), you are participating in a cooperative chain that manages validation, signals, and persistence. The official super() documentation has additional examples that complement this guide.

super() vs Explicit Calls: A Comparison

It is worth seeing the practical difference between using super() and calling the parent method explicitly:

class A:
    def action(self): print("A", end=" ")

class B(A): def action(self): A.action(self) # Explicit — ignores MRO print("B", end=" ")

class C(A): def action(self): A.action(self) # Explicit — ignores MRO print("C", end=" ")

class D(B, C): def action(self): B.action(self) # Only calls B and A, skips C print("D", end=" ")

D().action() # Output: "A B D" — C was skipped!

With super():

class A2: def action(self): print("A2", end=" ")

class B2(A2): def action(self): super().action() print("B2", end=" ")

class C2(A2): def action(self): super().action() print("C2", end=" ")

class D2(B2, C2): def action(self): super().action() print("D2", end=" ")

D2().action() # Output: "A2 C2 B2 D2" — correct!

The difference is clear: with explicit calls, you lose the fine-grained control that the MRO provides. With super(), the hierarchy is fully respected.

Conclusion

The super() function is an indispensable tool for anyone working with object-oriented programming in Python. It is not just syntactic sugar for calling parent methods — it is a sophisticated mechanism that enables cooperative multiple inheritance while keeping your code clean, modular, and maintainable.

Key takeaways:

  • super() follows the MRO, not just the direct parent class
  • Every class in the hierarchy must use super() for cooperation to work
  • The *args, **kwargs pattern is essential for flexible signatures
  • Inspect the MRO with Class.__mro__ when in doubt about execution order

After mastering super(), your next step is to deepen your knowledge of object-oriented programming in Python, exploring topics like metaclasses, descriptors, and advanced composition.

To go even further, check out PEP 3135 — New Super, which documents the simplified no-argument super() syntax introduced in Python 3. The Real Python tutorial on super() offers additional hands-on examples, and the Stack Overflow discussion on super() addresses the most common community questions.

Happy coding! 🐍