The super() Function in Python
A complete beginner-friendly guide to super() β covering how it works, Method Resolution Order (MRO), cooperative multiple inheritance, super() in constructors, common pitfalls, and why it's the standard way to work with parent classes in Python.
Last Updated
March 2026
Read Time
22 min
Level
Beginner to Intermediate
What is the super() Function in Python?
super() is a built-in Python function that returns a proxy object giving you access to methods defined in a class's parent (or sibling) classes β without needing to reference those parent classes by name directly. It is the standard, idiomatic way to call an overridden method's original implementation from within a subclass.
Think of super() like calling up the chain of command in an organisation. When a specific department overrides a general company policy but still wants to follow the core company-wide procedure first β say, logging a request before adding department-specific handling β it doesn't rewrite the entire company policy from scratch. It simply calls up to the parent process, lets it run, and then adds its own extra steps. super() is exactly this 'call up the chain' mechanism inside Python's class hierarchies.
The most common use of super() is inside an overridden __init__ constructor, where a subclass needs to run the parent class's initialisation logic (setting up inherited attributes) before adding its own subclass-specific setup. But super() works with any method, not just constructors β it can be used to extend any overridden method's behaviour rather than fully replacing it.
Crucially, super() does not simply mean "my direct parent class." In multiple inheritance scenarios, super() actually refers to the next class in the Method Resolution Order (MRO) β a specific, well-defined sequence computed by Python, which may not even be a direct parent of the current class. This subtlety is what makes super() genuinely powerful (enabling a pattern called cooperative multiple inheritance) but also a common source of confusion for developers coming from single-inheritance-only languages.
In modern Python β since Python 3, where the zero-argument form super() was introduced β calling the parent class is remarkably concise and readable compared to Python 2's older, more verbose syntax. This simplicity has made super() a staple of professional Python code, appearing constantly in Django models, custom exceptions, class-based views, and virtually every serious inheritance hierarchy.
Origins of super() β A Brief History
The story of Python's super() function is tightly linked to the broader evolution of Python's class model, and understanding it explains why the function behaves the way it does today.
- βΆ
Pre-2.2 β Explicit Parent Class Calls β In early Python, developers called a parent method by referencing the parent class explicitly by name: ParentClass.method(self, args) β functional, but verbose and fragile if the class hierarchy ever changed.
- βΆ
2001 β Python 2.2 Introduces super() β Alongside the introduction of 'new-style classes' (classes inheriting from object), Python 2.2 introduced the super() built-in, giving developers a cleaner, more maintainable way to reference parent class behaviour.
- βΆ
2001 β C3 Linearization Adopted β To make super() behave predictably even with multiple inheritance, Python adopted the C3 linearization algorithm (also used by languages like Perl 6/Raku and Dylan) to compute a consistent Method Resolution Order (MRO) for every class.
- βΆ
Python 2.x β Verbose Syntax β In Python 2, calling super() required explicitly passing both the current class and self: super(ClassName, self).method() β functional but repetitive, and error-prone if the class was ever renamed.
- βΆ
2008 β Python 3.0 Simplifies super() β Python 3 introduced the zero-argument form super(), which automatically infers the current class and instance using compiler-level 'implicit __class__ closure' magic, making calls dramatically cleaner and less error-prone.
- βΆ
2026 β Universal Modern Usage β Today, the zero-argument super() form is the standard, expected way to reference parent behaviour in virtually all modern Python 3 codebases, while the two-argument form remains available for rare edge cases (like calling a specific ancestor's method explicitly).
This history explains a subtlety many developers overlook: super() is not simply syntactic sugar for 'the parent class' β it is deeply tied to Python's MRO computation engine, which is precisely why it behaves so gracefully even in complex multiple-inheritance hierarchies.
Why super() Matters β Core Goals
Before exploring syntax in depth, it's worth understanding exactly what problems super() solves. These goals explain why it is considered essential in professional Python object-oriented code.
super() lets you call parent behaviour without writing the parent class's literal name, so renaming or restructuring the hierarchy later requires no changes to the calling code.
Instead of duplicating a parent's setup or logic inside every subclass, super() lets subclasses extend that logic with a single, clean call.
In multiple inheritance, super() correctly walks through every class in the Method Resolution Order, not just a single named parent β enabling reliable cooperative multiple inheritance.
Calling super().__init__() ensures that every class in the hierarchy gets a chance to set up its own attributes, avoiding half-initialised objects.
super() is the mechanism that makes Python's popular 'mixin' class pattern work correctly, letting independent behaviour chunks combine cleanly.
How super() Works β The Basics
Understanding super() requires understanding a few core mechanics: what it actually returns, how to call it with zero versus two arguments, and how it interacts with Python's Method Resolution Order.
π― super() Returns a Proxy Object
Calling super() does not return the parent class itself β it returns a special proxy object that knows how to correctly delegate attribute and method lookups to the next class in the current object's Method Resolution Order. This proxy is what makes chained super() calls across many classes in a hierarchy work correctly and predictably.
π ΎοΈ Zero-Argument Form (Python 3+)
In Python 3, simply writing super() inside a method automatically figures out both the current class and the current instance using compiler magic (an implicit __class__ cell created behind the scenes). This is by far the most common and recommended form in modern code: super().__init__(args).
βοΈ Two-Argument Form (Explicit)
The older, explicit form β super(CurrentClass, self) β is still valid in Python 3 and is occasionally used for advanced cases, such as deliberately skipping a class in the MRO or working with class methods where self isn't directly available. It requires manually specifying both the current class and the instance (or class, for classmethods).
π§ super() Follows the MRO, Not Just "The Parent"
This is the single most important subtlety: super() does not mean "my direct parent class" β it means "the next class after me in the current object's Method Resolution Order." In single inheritance these are the same thing, but in multiple inheritance they frequently are not, which is precisely what enables cooperative multiple inheritance.
How super() Resolves Its Target Method β Flowchart
When you call super().method() inside a class, Python performs a precise resolution process to determine exactly which class's version of method should run. The flowchart below traces this step by step.
Code Execution Flow β from source to output
Key insight: super() is resolved using the instance's actual, full MRO β not the class where super() is textually written. This is exactly why the same super().method() call inside Child can behave differently depending on which subclass actually created the instance, and it's the foundation of cooperative multiple inheritance.
super() in Action β Employee Hierarchy Example
The clearest way to demonstrate super() is a constructor chain, where each subclass adds its own attributes while still relying on the parent to set up shared ones.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def details(self):
return f"{self.name} earns {self.salary}"
class Manager(Employee):
def __init__(self, name, salary, team_size):
super().__init__(name, salary) # zero-argument form (Python 3)
self.team_size = team_size
def details(self):
base = super().details() # reuse parent's overridden method
return f"{base}, manages {self.team_size} people"
class SeniorManager(Manager):
def __init__(self, name, salary, team_size, region):
super().__init__(name, salary, team_size) # calls Manager.__init__
self.region = region
def details(self):
base = super().details() # calls Manager.details()
return f"{base}, overseeing the {self.region} region"
sm = SeniorManager("Ananya", 150000, 12, "South Asia")
print(sm.details())Output
Ananya earns 150000, manages 12 people, overseeing the South Asia regionPractice This Code β Live Editor
Line-by-Line Explanation
- βΆ
super().__init__(name, salary)in Manager β Calls Employee's constructor first, ensuring self.name and self.salary are set up correctly, before Manager adds its own self.team_size. - βΆ
super().details()in Manager β Calls Employee's details() method, reusing the 'name earns salary' string instead of rebuilding it manually, then appends Manager-specific information. - βΆ
super().__init__(name, salary, team_size)in SeniorManager β Chains one level further, calling Manager's constructor (which itself calls Employee's constructor via its own super() call). - βΆ
super().details()in SeniorManager β Calls Manager's details() (which itself calls Employee's details() internally), demonstrating how super() calls can chain cleanly across three levels of inheritance. - βΆ
The final output combines contributions from all three classes β Employee, Manager, and SeniorManager β each adding its own piece via a single super() call, with zero code duplication.
super() vs Calling the Parent Class Directly
Beginners sometimes wonder why they should use super() instead of simply calling ParentClass.method(self, args) directly. The example below demonstrates exactly why super() is the safer, more maintainable choice, especially in multiple inheritance.
class A:
def greet(self):
print("A.greet")
class B(A):
def greet(self):
print("B.greet")
A.greet(self) # hardcoded β fragile and can skip classes
class C(A):
def greet(self):
print("C.greet")
super().greet() # cooperative β respects the full MRO
class D(B, C):
def greet(self):
print("D.greet")
super().greet()
d = D()
d.greet()
print(D.__mro__)Output
D.greet B.greet A.greet (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)Notice something surprising: C.greet never gets printed, even though D inherits from C! This is because B.greet() calls A.greet(self) directly and explicitly, hardcoding class A and completely bypassing C in the MRO β even though the correct cooperative order should have been D β B β C β A. If B had instead used super().greet(), the call would have correctly continued on to C.greet() before finally reaching A.greet(), following the full MRO faithfully. This exact scenario is why super(), not hardcoded parent references, is the recommended and 'cooperative' approach in any hierarchy that might involve multiple inheritance.
Cooperative Multiple Inheritance with super()
Cooperative multiple inheritance is a design pattern where every class in a hierarchy uses super() consistently, allowing calls to smoothly chain through the entire Method Resolution Order rather than stopping at a single hardcoded parent. This pattern underlies Python's popular mixin class design.
class LoggingMixin:
def save(self):
print("Logging: about to save...")
super().save() # cooperatively continues the chain
class ValidationMixin:
def save(self):
print("Validating data before save...")
super().save()
class Model:
def save(self):
print("Saving to database.")
class UserModel(LoggingMixin, ValidationMixin, Model):
pass
user = UserModel()
user.save()Output
Logging: about to save... Validating data before save... Saving to database.This is the essence of the mixin pattern: independent, reusable behaviour chunks (LoggingMixin, ValidationMixin) each call super().save() cooperatively, letting Python's MRO chain them together automatically in the order they're listed in the subclass's inheritance list β UserModel(LoggingMixin, ValidationMixin, Model). Changing the order of mixins in that list would change the order these steps execute in, without touching any of their internal code.
The Two-Argument Form of super()
While the zero-argument super() is standard in modern Python 3 code, the older two-argument form β super(ClassName, instance_or_class) β remains valid and occasionally necessary, particularly in class methods or when deliberately targeting a specific point in the MRO.
In the vast majority of modern code, the zero-argument form is preferred for its brevity and reduced risk of typos (like accidentally writing the wrong class name). The two-argument form is mostly seen in legacy Python 2 codebases still being maintained, or advanced metaprogramming scenarios.
Advantages and Disadvantages of Using super()
super() is considered a best practice in nearly all Python OOP code, but it comes with a small learning curve and a few genuine pitfalls worth understanding.
super() Architecture β MRO Chain Diagram
The diagram below visualises how a chain of cooperative super() calls travels through a mixin-based class hierarchy, showing each layer's contribution before finally reaching the base implementation.
Each layer in this chain calls super() once and trusts the MRO Computation Engine to route the call correctly to the next layer β no layer needs to know which specific class comes after it. This decoupling is exactly what makes mixins reusable and reorderable.
Where super() is Used β Real-World Applications
super() is used constantly across professional Python codebases in 2026. Here are the major real-world contexts where it plays a central role:
- βΆ
ποΈ Django Model & Form Customisation β Developers routinely override save() or clean() on a Django model or form, calling super().save() or super().clean() first to preserve the framework's core behaviour before adding custom logic.
- βΆ
π Class-Based Views β Django and Flask class-based views frequently override methods like get_context_data(), calling super().get_context_data(**kwargs) to retain the framework's default context before adding custom template variables.
- βΆ
π Mixin-Based Design β Reusable mixin classes (like LoginRequiredMixin in Django) rely entirely on cooperative super() calls to chain their behaviour together with other mixins and the final base view class.
- βΆ
π§ͺ unittest.TestCase Setup Chains β Test classes overriding setUp() commonly call super().setUp() first to ensure any parent test fixture setup still runs before adding test-specific setup steps.
- βΆ
β οΈ Custom Exception Classes β Custom exceptions typically override __init__, calling super().__init__(message) to preserve standard Exception behaviour while attaching additional custom attributes like an error code.
- βΆ
π€ PyTorch Custom Neural Network Modules β Custom nn.Module subclasses in PyTorch call super().__init__() at the very start of their constructor, which is required for PyTorch's internal parameter-tracking machinery to work correctly.
- βΆ
π¦ Dataclasses and ORMs β Custom ORM model base classes and dataclass-like patterns often use super() to layer validation, serialization, or persistence behaviour across multiple cooperating base classes.
super().__init__() vs Manual Attribute Setup
A common beginner question is whether it's really necessary to call super().__init__(), or whether manually copying the parent's attribute assignments is just as good. This table clarifies the trade-offs.
Why Should You Master super() in 2026?
The super() function is a defining topic in nearly every Python OOP interview and appears constantly in real production code. Here's why mastering it deeply matters:
- βΆ
πΌ A Core OOP & Framework Interview Topic β super(), MRO, and cooperative multiple inheritance are frequently tested, especially for Django, Flask, or any framework-heavy engineering role.
- βΆ
ποΈ Essential for Working with Mixins β Nearly every serious Python framework relies on mixin classes that only work correctly if super() is used cooperatively throughout the hierarchy.
- βΆ
π Prevents Subtle, Hard-to-Trace Bugs β Misunderstanding how super() interacts with MRO is one of the most common sources of confusing bugs in multiple-inheritance codebases; mastering it prevents this entirely.
- βΆ
π A Sign of OOP Maturity β Correctly and confidently using super() across multi-level hierarchies is often seen by interviewers and reviewers as a clear signal of genuine OOP proficiency, beyond just knowing the syntax.
- βΆ
π Key to Extending Frameworks Safely β Whenever you customise behaviour in Django, Flask, PyTorch, or any framework's base classes, super() is what lets you extend rather than accidentally break the framework's internal expectations.
Common super() Mistakes Beginners Make
Even experienced developers occasionally misuse super(). Here are the mistakes that show up most often in real code reviews and beginner projects.
- βΆ
Forgetting to call super().__init__() entirely β Skipping this call means the parent class's attributes and setup logic never run, often producing confusing AttributeError exceptions later.
- βΆ
Assuming super() always means 'my direct parent' β In multiple inheritance, super() refers to the next class in the MRO, which can be a sibling class rather than the class literally written in the parentheses of the class definition.
- βΆ
Mixing hardcoded parent calls with super() calls β Using ParentClass.method(self) in one class and super().method() in another within the same hierarchy breaks the cooperative chain, as shown in classic MRO-skipping examples.
- βΆ
Forgetting **kwargs propagation in mixins β When mixins expect different constructor arguments, failing to properly pass along **kwargs through each super().__init__(**kwargs) call can cause TypeError exceptions deep in the chain.
- βΆ
Using the two-argument super() unnecessarily β Writing the verbose super(ClassName, self) form in modern Python 3 code when the simpler super() would work identically adds needless verbosity and a risk of typos if the class is ever renamed.
super() Interview Questions β Beginner to Intermediate
These are the most frequently asked interview questions on Python's super() function. Master these before any OOP or framework-focused interview.
Practice Questions β Test Your super() Knowledge
Test your understanding of Python's super() function with these practice questions. Try to answer each one before revealing the answer β active recall is the most effective way to learn.
1. What does super() return when called inside a method?
Easy2. Why is the zero-argument super() form preferred over the two-argument form in modern Python code?
Easy3. In a simple single-inheritance chain (Child inherits only from Parent), does super() behave any differently from calling Parent.method(self) directly?
Easy4. Given class D(B, C) where both B and C inherit from A, and B, C, and D all use super() consistently in an overridden method, in what order will their implementations run when D().method() is called?
Medium5. Why might using super(SpecificClass, self) instead of the standard super() ever be useful?
Medium6. Design two mixin classes, TimestampMixin and AuditMixin, that both need to call a shared save() method cooperatively, ensuring both mixins' logic runs before the final base Model.save().
Medium7. Why can inconsistent use of super() versus hardcoded parent calls silently break cooperative multiple inheritance, even though no error is raised?
Hard8. Explain why constructors in a mixin-heavy hierarchy often need to accept and forward **kwargs through super().__init__(**kwargs) rather than fixed positional arguments.
HardConclusion β Mastering super() in Python
The super() function is far more than a shortcut for 'call the parent class' β it is Python's gateway into cooperative, MRO-aware method resolution, and it is the mechanism that makes mixins, framework extension points, and multi-level inheritance hierarchies work reliably and predictably.
Understanding super() deeply means moving beyond 'it calls my parent' toward understanding Method Resolution Order, cooperative multiple inheritance, and why hardcoded parent references are fragile in comparison. These concepts together form some of the most professionally valuable object-oriented knowledge in the entire Python ecosystem.
As a next step, practice by building a small mixin-based hierarchy of your own β three or four classes that each add one piece of behaviour via super() β and print ClassName.__mro__ to see exactly how Python has ordered your classes. Watching the cooperative chain execute in the correct order is the clearest way to internalise how super() truly works.
super() is the quiet coordinator behind every well-behaved class hierarchy. Master it alongside encapsulation, abstraction, inheritance, and method overriding, and you will have a complete, professional command of Python's object-oriented foundations. πͺ