How to use 'super' in Python

Learn how to use Python's super() function with examples, tips, real-world applications, and common error debugging. Master inheritance today.

How to use 'super' in Python
Published on: 
Tue
Mar 17, 2026
Updated on: 
Tue
Mar 24, 2026
The Replit Team

Python's super() function is a key tool for object-oriented programming. It lets you call methods from a parent class, which simplifies inheritance and makes your code more reusable.

In this article, you'll explore techniques to use super() effectively. You'll get practical tips, real-world application examples, and debugging advice to master its use in your projects.

Basic usage of super()

class Parent:
def greet(self):
return "Hello from Parent"

class Child(Parent):
def greet(self):
parent_greeting = super().greet()
return f"{parent_greeting} and Child"

child = Child()
print(child.greet())--OUTPUT--Hello from Parent and Child

In this example, the Child class inherits from the Parent class. When you call the greet method on a Child object, it doesn't just replace the parent's method. Instead, it uses super().greet() to first execute the greet method from the Parent class.

This technique allows you to extend the parent's functionality rather than completely overwriting it. The Child class can then add its own logic, combining the parent's greeting with its own message. It’s a clean way to build on existing code and maintain a clear relationship between classes.

Common inheritance patterns with super()

Building on this basic method call, super() is also essential for common patterns like initializing constructors, managing multiple inheritance, and using its explicit form.

Using super() in __init__ methods

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

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

fido = Dog("Fido", "Golden Retriever")
print(f"{fido.name} is a {fido.breed}")--OUTPUT--Fido is a Golden Retriever

Using super() in the __init__ method is a fundamental pattern for initializing inherited classes. It ensures the parent class’s constructor runs first, setting up its attributes before the child class adds its own.

  • In the Dog class, super().__init__(name) calls the Animal constructor to handle the name attribute.
  • This avoids duplicating initialization logic from the parent class.
  • The Dog constructor can then focus only on what makes it unique, like setting the breed.

Using super() with multiple inheritance

class A:
def method(self):
return "A's method"

class B(A):
def method(self):
return f"B's method, then {super().method()}"

class C(A):
def method(self):
return f"C's method, then {super().method()}"

class D(B, C):
def method(self):
return f"D's method, then {super().method()}"

d = D()
print(d.method())
print(D.__mro__)--OUTPUT--D's method, then B's method, then C's method, then A's method
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

In multiple inheritance, super() follows a specific path called the Method Resolution Order (MRO). This isn't just about calling a parent class; it’s about following a predictable chain that ensures every class in the hierarchy is called exactly once.

  • When you call d.method(), Python uses the MRO to determine the call sequence. The D.__mro__ attribute shows this path is D, then B, then C, and finally A.
  • Each super().method() call passes control to the next class in the MRO, allowing each one to contribute before the next.

The explicit form of super()

class Parent:
def greet(self):
return "Hello from Parent"

class Child(Parent):
def greet(self):
# Explicitly specify class and instance
return super(Child, self).greet() + " via Child"

child = Child()
print(child.greet())--OUTPUT--Hello from Parent via Child

While the argument-less super() is common, you'll also see its explicit form: super(ClassName, self). This version requires you to specify the class where the method search should start and the instance it's acting on. It's the original syntax from older Python versions.

  • The first argument, Child here, tells super() to find the method in the class that comes after Child in the Method Resolution Order.
  • The second argument, self, provides the instance context.
  • Though modern Python allows the simpler super(), you'll see this form in legacy code or certain advanced cases.

Advanced super() techniques

Beyond these common patterns, super() also shines in more specialized scenarios like class methods, property decorators, and complex cooperative inheritance designs.

Using super() in class methods

class Base:
@classmethod
def create(cls, value):
return cls(value)

def __init__(self, value):
self.value = value

class Derived(Base):
@classmethod
def create(cls, value):
instance = super().create(value)
instance.value *= 2
return instance

derived = Derived.create(5)
print(derived.value)--OUTPUT--10

You can also use super() inside a @classmethod. This pattern is especially handy for factory methods, where you want to extend a parent's object creation logic instead of rewriting it.

  • In the Derived class, super().create(value) calls the create method from the Base class.
  • Crucially, this call uses Derived as the class (cls) for instantiation, so you get a Derived object back.
  • The child class can then perform additional setup on the new instance before returning it.

Using super() with property decorators

class Person:
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"

class Employee(Person):
@property
def full_name(self):
return f"{super().full_name} (Staff)"

employee = Employee()
employee.first_name = "John"
employee.last_name = "Doe"
print(employee.full_name)--OUTPUT--John Doe (Staff)

You can also use super() to extend properties defined with the @property decorator. It works just like calling a parent method. In the Employee class, the full_name property accesses the parent's logic by calling super().full_name. This lets you reuse the base implementation without rewriting it.

  • The Employee class first gets the name "John Doe" from the Person property.
  • It then adds its own text, (Staff), to create the final output.

This pattern is great for adding extra details to inherited attributes while keeping your code clean.

Cooperative inheritance with super()

class Sized:
def __init__(self):
self.size = 0

def grow(self, amount):
self.size += amount
return self.size

class Visible:
def __init__(self):
super().__init__()

def grow(self, amount):
result = super().grow(amount * 2)
print(f"New size: {result}")
return result

class Widget(Visible, Sized):
pass

widget = Widget()
widget.grow(5)--OUTPUT--New size: 10

This pattern, known as cooperative inheritance, is where classes are designed to work together in a chain. Each class uses super() to call the next method in the Method Resolution Order, allowing them to build on each other's functionality instead of just replacing it.

  • When you call widget.grow(5), it triggers Visible.grow() first because of the inheritance order in the Widget class.
  • Inside Visible.grow(), it doubles the input and then uses super() to pass control to Sized.grow().
  • Sized.grow() performs the final size calculation, and the result is passed back up the chain.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. You can describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

For the super() techniques we've explored, Replit Agent can turn them into production-ready tools. For example, you could:

  • Build an e-commerce inventory system where specialized items like Apparel and Electronics inherit from a base Product class, using super() to initialize common attributes.
  • Create a multi-channel notification service that combines Email, SMS, and Push notification behaviors into a single class, using super() to ensure all delivery methods are called.
  • Deploy a data serialization utility that extends a base JSONSerializer to add custom fields or formats for different data models, calling the parent's logic via super().

Describe your app idea, and Replit Agent will write the code, test it, and fix issues automatically, all from your browser.

Common errors and challenges

While super() is powerful, a few common pitfalls can trip you up, often leading to unexpected errors during execution.

Forgetting to call super().__init__() in subclasses

One of the most frequent mistakes is forgetting to call super().__init__() inside a subclass's constructor. When this happens, the parent class is never properly initialized.

  • This means any attributes set in the parent's __init__ method won't exist on the subclass instance.
  • Attempting to access them later in your code will almost certainly raise an AttributeError, as the attribute was never created.

Incorrect method signature when using super()

Another common issue arises when the arguments in a subclass method don't match the parent method it's calling via super(). The method signatures must be compatible.

  • For example, if a parent method expects two arguments but the child's super() call provides only one, you'll get a TypeError.
  • This happens because you're effectively calling the parent method with the wrong number of arguments, breaking the chain of command.

Using super() in a class with no parent method

You can also run into trouble by calling super() for a method that doesn't exist in any of the parent classes in the Method Resolution Order (MRO).

  • When Python traverses the MRO and can't find the specified method, it raises an AttributeError.
  • This is especially common in complex multiple inheritance scenarios where the call chain isn't immediately obvious, making it easy to assume a method exists when it doesn't.

Forgetting to call super().__init__() in subclasses

It's easy to forget the super().__init__() call in a subclass, but doing so prevents the parent class from initializing. This means attributes from the parent are never created on the child instance, causing an AttributeError. See what happens below.

class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model

class Car(Vehicle):
def __init__(self, make, model, year):
# Missing super().__init__() call
self.year = year

def get_info(self):
return f"{self.make} {self.model} ({self.year})"

car = Car("Toyota", "Corolla", 2020)
print(car.get_info()) # Will raise AttributeError

The get_info() method fails because the Car instance never receives the make and model attributes. Its constructor only sets year, triggering an AttributeError. The following code demonstrates the proper implementation.

class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model

class Car(Vehicle):
def __init__(self, make, model, year):
super().__init__(make, model) # Call parent's __init__
self.year = year

def get_info(self):
return f"{self.make} {self.model} ({self.year})"

car = Car("Toyota", "Corolla", 2020)
print(car.get_info()) # Works correctly

The fix is to call super().__init__(make, model) inside the Car class’s constructor. This delegates the initialization of make and model to the parent Vehicle class. Once the parent’s __init__ method runs, the attributes are set correctly on the instance. As a result, the get_info() method can access them without raising an AttributeError. This is a crucial step anytime you override a parent's constructor.

Incorrect method signature when using super()

When a subclass method calls super(), its signature must be compatible with the parent method. If the arguments don't align, Python raises a TypeError because the parent method receives the wrong inputs. The following code demonstrates this common error.

class Shape:
def calculate_area(self, width, height):
return width * height

class Rectangle(Shape):
def calculate_area(self): # Missing parameters
return super().calculate_area() # Will raise TypeError

rect = Rectangle()
print(rect.calculate_area())

The Rectangle.calculate_area method is defined without parameters, so its call to super().calculate_area() passes no arguments. This conflicts with the parent method, which requires both width and height. The following code shows the correct implementation.

class Shape:
def calculate_area(self, width, height):
return width * height

class Rectangle(Shape):
def calculate_area(self, width, height): # Correct signature
return super().calculate_area(width, height)

rect = Rectangle()
print(rect.calculate_area(5, 10))

The fix is to align the method signatures. By updating Rectangle.calculate_area to accept width and height, you can forward them with super().calculate_area(width, height). This gives the parent method the arguments it needs, resolving the TypeError. It’s a common issue when refactoring a parent class, so always ensure child method signatures remain compatible to keep the inheritance chain intact.

Using super() in a class with no parent method

Calling super() for a method that doesn't exist in any parent class raises an AttributeError. Python searches the Method Resolution Order (MRO), and if the method isn't found anywhere in the chain, the call fails. The following code demonstrates this.

class Base:
def __init__(self, value):
self.value = value

class Derived(Base):
def process(self):
# Error: process() doesn't exist in parent class
return super().process() + " enhanced"

obj = Derived(10)
print(obj.process())

The Derived class tries to call super().process(), but its parent, Base, doesn't define a process() method. This breaks the call chain, causing the error. See how to fix this by ensuring the parent method exists.

class Base:
def __init__(self, value):
self.value = value

def process(self):
return f"Processed value: {self.value}"

class Derived(Base):
def process(self):
return super().process() + " enhanced"

obj = Derived(10)
print(obj.process())

The fix is to ensure the method you're calling with super() actually exists in a parent class. By adding a process() method to the Base class, the AttributeError is resolved because the call from Derived now has a valid target in the Method Resolution Order.

  • This error often appears in complex inheritance chains, so it’s a good idea to double-check that the method is available somewhere up the hierarchy.

Real-world applications

Beyond just fixing errors, super() is essential for building real-world features like extensible form validators and cooperative caching mechanisms.

Using super() with form validate() method

With super(), you can build powerful validation chains where a child class's validate() method adds more specific checks on top of the base validation provided by its parent.

class BaseValidator:
def validate(self, value):
if not value:
return False
return True

class EmailValidator(BaseValidator):
def validate(self, value):
if not super().validate(value):
return False
return '@' in value and '.' in value.split('@')[1]

validator = EmailValidator()
print(validator.validate("[email protected]"))
print(validator.validate("invalid-email"))

In this example, the EmailValidator’s validate() method first calls super().validate(value). This delegates the initial check to the BaseValidator, which simply confirms the value isn't empty. This ensures the basic validation runs before any specialized logic.

  • If that check succeeds, the EmailValidator proceeds with its own rules.
  • It verifies the presence of an @ symbol and a . in the domain part of the string.

This cooperative design prevents code duplication by layering validation rules from general to specific.

Implementing timed caching with super().get() and super().set()

Similarly, you can use super() to extend a simple key-value cache, adding time-to-live (TTL) logic to the get() and set() methods without duplicating the parent's storage logic.

class Cache:
def __init__(self):
self.storage = {}

def get(self, key, default=None):
return self.storage.get(key, default)

def set(self, key, value):
self.storage[key] = value

class TimedCache(Cache):
def __init__(self):
super().__init__()
self.timestamps = {}

def set(self, key, value, ttl=60):
import time
super().set(key, value)
self.timestamps[key] = time.time() + ttl

def get(self, key, default=None):
import time
if key in self.timestamps and time.time() > self.timestamps[key]:
return default
return super().get(key, default)

cache = TimedCache()
cache.set("api_result", "some data", ttl=2)
print(cache.get("api_result"))
import time
time.sleep(3) # Wait for TTL to expire
print(cache.get("api_result"))

The TimedCache class inherits from Cache to add expiration logic. Its set() method calls super().set() to store the data, then records an expiration timestamp. This lets you define how long an item should remain valid.

  • When you call get(), it first checks if the item's timestamp has passed.
  • If the item is expired, it returns the default value. Otherwise, it calls super().get() to retrieve the data from the parent's storage.

This design creates a cache where items automatically disappear after a set duration.

Get started with Replit

Turn these super() techniques into a real tool. Describe what you want to build, like “a form validator with a base class and an email subclass” or “a caching system with timed expiration logic.”

Replit Agent will write the code, test for errors, and deploy your app. Start building with Replit.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started for free

Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.