Viorel Buliga
About me
← All articles
PythonOOPBackend

Python Inheritance Explained: Simple, Chain, Hierarchical and Multiple

July 2, 2026·7 min read
Quick answer: Python supports four types of inheritance - simple (one parent), chain (a subclass becomes a parent itself), hierarchical (multiple subclasses from the same parent), and multiple (one class inherits from more than one parent). When method names conflict in multiple inheritance, Python uses the MRO (Method Resolution Order) to decide which version to call.

When you start working with classes, inheritance feels like a simple concept - one class extends another and that is it. But as systems grow, the relationships between classes naturally become richer. You end up with deeper hierarchies, branching structures, and classes that combine behaviour from multiple sources. Python handles all of these cases, and understanding each form of inheritance helps you build code that is flexible, clean, and easy to extend.

Python inheritance types illustration

What is simple inheritance?

Simple inheritance is the most basic form - one base class, one subclass. The subclass gets everything the parent has and can add or override whatever it needs.

Imagine a notification system. We have a general Notification class that stores a title and a message. Then we create an EmailNotification subclass that adds a recipient address and a method to send the email.

class Notification:
    def __init__(self, title, message):
        self.title = title
        self.message = message

    def summary(self):
        print(f"[{self.title}] {self.message}")


class EmailNotification(Notification):
    def __init__(self, title, message, recipient):
        super().__init__(title, message)
        self.recipient = recipient

    def send(self):
        print(f"Sending email to {self.recipient}:")
        self.summary()


n = EmailNotification("Welcome", "Thanks for signing up!", "user@example.com")
n.send()

Output:

Sending email to user@example.com:
[Welcome] Thanks for signing up!

EmailNotification does not redefine summary() because it does not need to - it works perfectly as inherited. It only adds what is specific to email: a recipient and a send() method.

Here is another example. A Product class holds the name and price of any product. A DigitalProduct inherits from it and adds a download link - the only thing that makes a digital product different:

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def display(self):
        print(f"{self.name} - ${self.price}")


class DigitalProduct(Product):
    def __init__(self, name, price, download_url):
        super().__init__(name, price)
        self.download_url = download_url

    def display(self):
        super().display()
        print(f"Download: {self.download_url}")


p = DigitalProduct("Python Course", 49, "https://example.com/download/python-course")
p.display()

Output:

Python Course - $49
Download: https://example.com/download/python-course

display() is extended here, not replaced. We call the parent version first with super(), then add the download link. If Product.display() ever changes, DigitalProduct stays compatible automatically.

A DigitalProduct is a Product - just one that knows more things. The code reflects exactly that idea, without duplication.

What is chain inheritance?

Things get more interesting when a subclass itself becomes a parent. This is chain inheritance (also called multilevel inheritance). The relationship goes downward like a family tree - each level inherits everything from the one above and adds something new.

Consider this structure:

  • Person is the base class, storing a name.
  • Employee inherits from Person and adds a job title.
  • Manager inherits from Employee and adds a team size.
class Person:
    def __init__(self, name):
        self.name = name

    def introduce(self):
        print(f"Hi, I'm {self.name}.")


class Employee(Person):
    def __init__(self, name, title):
        super().__init__(name)
        self.title = title

    def introduce(self):
        super().introduce()
        print(f"I work as an {self.title}.")


class Manager(Employee):
    def __init__(self, name, title, team_size):
        super().__init__(name, title)
        self.team_size = team_size

    def introduce(self):
        super().introduce()
        print(f"I manage a team of {self.team_size} people.")


m = Manager("Sarah", "Engineering Manager", 8)
m.introduce()

Output:

Hi, I'm Sarah.
I work as an Engineering Manager.
I manage a team of 8 people.

Each level adds depth while keeping everything from the levels above. super() ensures the chain is called in order, so no information is lost.

What is hierarchical inheritance?

In real life, one base concept can branch in multiple directions. Hierarchical inheritance is exactly that - several subclasses all inheriting from the same parent, each going their own way.

A payment system is a good example. A Payment base class holds the amount and a method to show it. Then CardPayment, CashPayment, and BankTransfer all inherit from it, each adding their own logic:

class Payment:
    def __init__(self, amount):
        self.amount = amount

    def display(self):
        print(f"Payment of ${self.amount}")


class CardPayment(Payment):
    def __init__(self, amount, card_last4):
        super().__init__(amount)
        self.card_last4 = card_last4

    def display(self):
        super().display()
        print(f"Paid by card ending in {self.card_last4}")


class CashPayment(Payment):
    def display(self):
        super().display()
        print("Paid in cash")


class BankTransfer(Payment):
    def __init__(self, amount, iban):
        super().__init__(amount)
        self.iban = iban

    def display(self):
        super().display()
        print(f"Transferred to IBAN: {self.iban}")


payments = [
    CardPayment(120, "4242"),
    CashPayment(35),
    BankTransfer(500, "RO49AAAA1B31007593840000"),
]

for p in payments:
    p.display()
    print()

All three classes share the common foundation from Payment. Each one then adds its own rules. This structure works well whenever you have a clear common base and multiple specialisations that go in different directions.

Pattern recognition: when you have a parent class with clearly defined shared rules and multiple subclasses that each add their own behaviour, you have hierarchical inheritance. It is one of the cleanest and most maintainable structures in OOP.

What is multiple inheritance?

Multiple inheritance allows a class to inherit from more than one parent at the same time, combining behaviour from different sources. It is powerful, but needs to be used carefully.

Imagine a reporting system. We have a Trackable class that logs when an object was last updated, and an Exportable class that knows how to export data as CSV. A Report class needs both:

class Trackable:
    def mark_updated(self):
        from datetime import datetime, timezone
        self.updated_at = datetime.now(timezone.utc).isoformat()
        print(f"Updated at {self.updated_at}")


class Exportable:
    def export_csv(self):
        print(f"Exporting {self.__class__.__name__} as CSV...")


class Report(Trackable, Exportable):
    def __init__(self, title):
        self.title = title

    def generate(self):
        print(f"Generating report: {self.title}")
        self.mark_updated()


r = Report("Monthly Sales")
r.generate()
r.export_csv()

Output:

Generating report: Monthly Sales
Updated at 2026-07-02T10:30:00.123456+00:00
Exporting Report as CSV...

Report gets the full behaviour of both parent classes. The order of parents in the class definition (Trackable before Exportable) matters - it determines the search order when methods have the same name.

What is the MRO and how does Python resolve conflicts?

Everything looks simple until two parent classes define the same method with different implementations. Python needs a clear rule to decide which version to call. That rule is the MRO - Method Resolution Order.

Think of MRO as a search path. When you call a method, Python walks down that path and uses the first matching version it finds. The path is computed automatically using the C3 linearization algorithm, which guarantees a consistent and predictable order.

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

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

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

class D(B, C):
    pass

print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

d = D()
d.process()  # B - because B comes before C in the MRO

Python searches in this order: D first, then B, then C, then A. The left parent always has priority, but each class appears only once in the chain. You can inspect the MRO of any class with ClassName.__mro__.

Why super() is preferred: in multiple inheritance, super() does not call the direct parent - it calls the next class in the MRO. This is what makes cooperative inheritance work. If every class in the chain uses super() consistently, all of them get called in the right order. One class that skips super() silently breaks the chain.

When should you use inheritance vs composition?

Inheritance is not the right tool for every relationship between classes. Before reaching for it, ask: does class B have an "is a type of" relationship with class A? If yes, inheritance makes sense. If not, prefer composition.

  • A Manager is an Employee - inheritance makes sense.
  • A CardPayment is a Payment - inheritance makes sense.
  • A NotificationService uses an EmailSender - composition makes sense.
# BAD - multiple inheritance with conflicting method signatures
class EmailSender:
    def send(self, to, subject, body): ...

class SmsSender:
    def send(self, to, message): ...

class NotificationService(EmailSender, SmsSender):  # which send()?
    pass

# BETTER - composition, clear and explicit
class NotificationService:
    def __init__(self):
        self.email = EmailSender()
        self.sms = SmsSender()

    def notify_email(self, to, subject, body):
        self.email.send(to, subject, body)

    def notify_sms(self, to, message):
        self.sms.send(to, message)

Multiple inheritance works best for Mixins - small, focused classes that add a single, self-contained behaviour (logging, serialisation, timestamps). It becomes a problem when parent classes share state, have conflicting method signatures, or when the hierarchy becomes too deep to reason about.

Frequently asked questions

What are the four types of inheritance in Python?

Simple (one parent, one subclass), chain or multilevel (a subclass becomes a parent itself), hierarchical (multiple subclasses from the same parent), and multiple (one class inherits from more than one parent).

What is the MRO in Python?

The Method Resolution Order is the search path Python follows when looking up a method in a class hierarchy. It is computed automatically using the C3 linearization algorithm and guarantees a consistent, predictable order. You can inspect it with ClassName.__mro__.

Does the order of parent classes in multiple inheritance matter?

Yes. The order in the class definition determines the MRO. Python searches parent classes left to right. If two parents define the same method, the one listed first wins. Always put the most specific class first.

Why should I use super() instead of calling the parent class directly?

In multiple inheritance, super() calls the next class in the MRO, not necessarily the direct parent. This is what makes cooperative inheritance work - all classes in the chain get called in the right order. Calling a parent class directly by name breaks this and can silently skip parts of the hierarchy.

When should I use multiple inheritance?

The safest and most common use case is the Mixin pattern - small classes that add one specific capability (logging, serialisation, timestamps) without defining a primary identity. Avoid multiple inheritance when parent classes share state, have conflicting methods, or when the combination does not have a clear conceptual meaning.

What is the difference between chain inheritance and hierarchical inheritance?

Chain (multilevel) inheritance goes vertically - A inherits from B, and B inherits from C, forming a single line. Hierarchical inheritance goes horizontally - multiple classes all inherit from the same single parent, branching out in different directions.

Official resources

  • Inheritance - Python Docs
  • Multiple Inheritance - Python Docs
  • super() built-in - Python Docs
  • The Python 2.3 Method Resolution Order - Python.org
Share on LinkedIn