Placement Prep

Inheritance in Python: Types, MRO, and Working Examples

Python supports 5 inheritance types. This guide covers single, multiple, multilevel, hierarchical, and hybrid inheritance with MRO, super(), and placement-round patterns.

By FACE Prep Team 5 min read
python inheritance oop multiple-inheritance method-resolution-order placement-prep python-oop

Inheritance lets a Python class reuse attributes and methods from another class without rewriting them.

What Inheritance Does in Python

When you write class Engineer(Employee):, Python gives Engineer access to every attribute and method defined in Employee. The child class can override any method, add new ones, or use the parent’s implementation as-is. This single mechanism drives most of the code reuse in object-oriented Python.

Three things happen at class definition time:

  • Python stores the parent reference in the child’s __bases__ tuple.
  • Every method and attribute from the parent becomes accessible on child instances via the standard lookup chain.
  • If the child defines a method with the same name, the child’s version takes priority (method overriding).

The syntax is minimal:

class Employee:
    def __init__(self, name, emp_id):
        self.name = name
        self.emp_id = emp_id

    def summary(self):
        return f"{self.name} (ID: {self.emp_id})"


class Engineer(Employee):
    def __init__(self, name, emp_id, domain):
        super().__init__(name, emp_id)
        self.domain = domain

    def summary(self):
        return f"{super().summary()}{self.domain}"


dev = Engineer("Arun", "E1042", "backend")
print(dev.summary())
# Output: Arun (ID: E1042) — backend

Engineer inherits Employee’s __init__ logic via super().__init__(), then adds its own domain attribute. The summary() method overrides the parent version while still referencing it through super(). This pattern of extending rather than replacing parent behaviour is idiomatic Python. You keep the parent’s logic intact and layer additional behaviour on top.

The Five Inheritance Types

Python supports five inheritance patterns. Each describes a different shape of class relationship.

Single Inheritance

One child, one parent. The simplest form.

class Vehicle:
    def start(self):
        return "Engine started"

class Car(Vehicle):
    def drive(self):
        return "Car moving"

c = Car()
print(c.start())  # Engine started
print(c.drive())  # Car moving

Multiple Inheritance

One child inherits from two or more parents.

class GPSMixin:
    def locate(self):
        return "GPS coordinates acquired"

class Vehicle:
    def start(self):
        return "Engine started"

class DeliveryTruck(Vehicle, GPSMixin):
    def deliver(self):
        return "Package delivered"

truck = DeliveryTruck()
print(truck.start())   # Engine started
print(truck.locate())  # GPS coordinates acquired

DeliveryTruck combines behaviour from both Vehicle and GPSMixin. The order in the parentheses matters for MRO (covered in the next section).

Multilevel Inheritance

A chain: grandparent to parent to child.

class Vehicle:
    def start(self):
        return "Engine started"

class Car(Vehicle):
    def drive(self):
        return "Car moving at 60 km/h"

class ElectricCar(Car):
    def charge(self):
        return "Battery charging"

ec = ElectricCar()
print(ec.start())   # Engine started (from Vehicle)
print(ec.drive())   # Car moving at 60 km/h (from Car)
print(ec.charge())  # Battery charging (own method)

ElectricCar reaches Vehicle.start() through Car, not directly.

Hierarchical Inheritance

Multiple children share one parent.

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

    def role(self):
        return "General employee"

class Developer(Employee):
    def role(self):
        return f"{self.name}: Developer"

class Analyst(Employee):
    def role(self):
        return f"{self.name}: Analyst"

d = Developer("Priya")
a = Analyst("Karthik")
print(d.role())  # Priya: Developer
print(a.role())  # Karthik: Analyst

Both Developer and Analyst inherit from Employee but override role() differently.

Hybrid Inheritance

A combination of multiple and multilevel (or hierarchical) inheritance. The classic case forms a diamond shape.

class Vehicle:
    def __init__(self):
        self.fuel_type = "generic"
        print("Vehicle.__init__")

class ElectricVehicle(Vehicle):
    def __init__(self):
        super().__init__()
        self.fuel_type = "electric"
        print("ElectricVehicle.__init__")

class PassengerVehicle(Vehicle):
    def __init__(self):
        super().__init__()
        self.seats = 5
        print("PassengerVehicle.__init__")

class ElectricCar(ElectricVehicle, PassengerVehicle):
    def __init__(self):
        super().__init__()
        print("ElectricCar.__init__")

ec = ElectricCar()

Output:

  • Vehicle.__init__
  • PassengerVehicle.__init__
  • ElectricVehicle.__init__
  • ElectricCar.__init__

Vehicle.__init__ runs only once despite appearing in both parent chains. That is MRO at work.

TypeStructureUse when
SingleA → BExtending one base class
MultipleA, B → CCombining orthogonal behaviours (mixins)
MultilevelA → B → CBuilding specialisation chains
HierarchicalA → B, A → CShared base with diverging specialisations
HybridDiamond or complex graphReal-world models with overlapping hierarchies

Method Resolution Order and the Diamond Problem

When ElectricCar calls a method, Python needs a rule for which parent to check first. That rule is MRO, computed via C3 linearisation.

Inspect it directly:

print(ElectricCar.__mro__)

Output:

(<class 'ElectricCar'>, <class 'ElectricVehicle'>,
 <class 'PassengerVehicle'>, <class 'Vehicle'>, <class 'object'>)

The algorithm guarantees three properties:

  • A child always appears before its parents.
  • The left-to-right order in the class definition is preserved.
  • If a consistent ordering is impossible (conflicting parent orders), Python raises TypeError at class definition time.

ElectricVehicle comes before PassengerVehicle because it was listed first in class ElectricCar(ElectricVehicle, PassengerVehicle). If you swap the order in the definition, MRO flips accordingly.

The super() Bug with Direct Parent Calls

A common mistake in multiple inheritance is calling the parent __init__ directly instead of using super():

# BUG: skips MRO, calls Vehicle.__init__ twice
class ElectricVehicle(Vehicle):
    def __init__(self):
        Vehicle.__init__(self)  # direct call
        self.fuel_type = "electric"

class PassengerVehicle(Vehicle):
    def __init__(self):
        Vehicle.__init__(self)  # direct call
        self.seats = 5

class ElectricCar(ElectricVehicle, PassengerVehicle):
    def __init__(self):
        ElectricVehicle.__init__(self)
        PassengerVehicle.__init__(self)

Here Vehicle.__init__ runs twice. In a real codebase with database connections or file handles opened in __init__, this causes duplicate resource allocation.

The fix: replace every Parent.__init__(self) with super().__init__(). Then MRO ensures each class in the chain is initialised exactly once.

Edge Cases and Common Mistakes

__bases__ vs __mro__

print(ElectricCar.__bases__)
# (<class 'ElectricVehicle'>, <class 'PassengerVehicle'>)

print(ElectricCar.__mro__)
# (<class 'ElectricCar'>, <class 'ElectricVehicle'>,
#  <class 'PassengerVehicle'>, <class 'Vehicle'>, <class 'object'>)

__bases__ shows only the immediate parents. __mro__ shows the complete lookup chain. Interview output-prediction questions often test this distinction.

isinstance() vs type()

ec = ElectricCar()

print(isinstance(ec, Vehicle))      # True
print(type(ec) == Vehicle)           # False
print(type(ec) == ElectricCar)       # True

isinstance() walks the inheritance hierarchy. type() checks the exact class. Use isinstance() when you care about substitutability (“is this object usable as a Vehicle?”); use type() when you need an exact-class match. Most production code and placement-round answers expect isinstance() for type-checking.

Abstract Base Classes and Inheritance

The abc module enforces method contracts across inheritance chains:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h

# Shape() would raise TypeError
r = Rectangle(4, 5)
print(r.area())  # 20

If Rectangle forgot to define area(), Python raises TypeError at instantiation, not at the method call. This enforcement happens even if area() is never called at runtime. The abstract contract is checked structurally, not dynamically. This is how large codebases enforce interface compliance in inherited hierarchies without relying on integration tests to catch missing methods.

Where Inheritance Shows Up in Placement Rounds

OOP theory is a recurring section in written rounds for service-tier companies. The patterns:

  • Output-prediction on MRO (which __init__ prints first)
  • MCQs on super() behaviour in multilevel chains
  • Code-correction questions where Parent.__init__(self) causes a bug

These questions test whether you can trace execution order, not whether you memorised definitions. If you can read ClassName.__mro__ and predict output, you clear that section reliably. The typical format is a 4-class diamond hierarchy printed to stdout, with the question asking you to sequence the print statements.

For algorithmic rounds that follow the OOP section, practise problems like finding array extremes or palindrome checks where clean class design keeps your solution readable. Common data structure interview questions also assume you can wrap solutions in well-structured classes.

The diamond-problem example from this article (ElectricCar inheriting from both ElectricVehicle and PassengerVehicle) is a pattern you can build, break, and debug interactively.

  • TinkerLLM gives you a prompt-driven Python sandbox for exactly that kind of MRO experiment at ₹299.

Primary sources

Frequently asked questions

What is the difference between __bases__ and __mro__ in Python?

__bases__ returns only the immediate parent classes listed in the class definition. __mro__ returns the entire method resolution chain including all ancestors up to object, computed by C3 linearisation.

Can a child class inherit from more than two parents in Python?

Yes. Python places no hard limit on the number of parents. The class definition lists them separated by commas: class Child(A, B, C, D). MRO still resolves method lookup in a deterministic left-to-right, depth-first order via C3 linearisation.

What happens if two parent classes define the same method name?

Python uses MRO to decide. It calls the method from whichever parent appears first in the resolution order. You can inspect the order by printing ChildClass.__mro__ and checking which parent comes earlier in the tuple.

Does Python support private inheritance like C++ does?

No. Python has no access-specifier-based inheritance modes (public, protected, private) like C++. All inherited attributes and methods are accessible from the child class. Name mangling with a __ prefix is the closest equivalent, but it is not true access restriction.

How does super() know which parent to call in multiple inheritance?

super() follows the MRO. It does not simply call the immediate parent in the source code. It calls the next class in the __mro__ tuple relative to the class where super() is invoked. This ensures every class in the chain is called exactly once.

Build AI projects

A self-paced playground for building with LLMs.

TinkerLLM is FACE Prep's sister property. A guided environment for shipping real LLM applications, the kind of project that earns a paragraph on your resume, not a line.

Try TinkerLLM (₹299 launch)
Free AI Roadmap PDF