Skills101

OOP Concepts · Lesson 3 of 8 · Updated ·

Inheritance in OOP — Types, the Diamond Problem and When Not to Use It

Inheritance lets a new class (child, subclass) reuse the fields and methods of an existing class (parent, superclass) and add or change behaviour. It models an "is-a" relationship: a Car is a Vehicle.

1. A simple example

class Vehicle:
    def __init__(self, brand, wheels):
        self.brand, self.wheels = brand, wheels

    def describe(self):
        return f"{self.brand} with {self.wheels} wheels"

class Bike(Vehicle):
    def __init__(self, brand):
        super().__init__(brand, 2)        # reuse the parent's setup

class Truck(Vehicle):
    def __init__(self, brand, load_tonnes):
        super().__init__(brand, 6)
        self.load = load_tonnes

    def describe(self):                   # override and extend
        return super().describe() + f", carries {self.load} t"

print(Bike("Hero").describe())
print(Truck("Tata", 10).describe())
print(isinstance(Bike("Bajaj"), Vehicle))
Hero with 2 wheels
Tata with 6 wheels, carries 10 t
True
  • super() calls the parent's version.
  • Overriding = the child provides its own version of a parent method.

2. Types of inheritance

Single (A → B), multilevel (A → B → C), hierarchical (A → B and C) and multiple (A and B → C) inheritance

TypeShapeSupported in
SingleA → Ball
MultilevelA → B → Call
HierarchicalA → B, A → Call
MultipleA + B → CPython, C++ (Java/C# only via interfaces)
Hybrida mix of the abovewhere multiple is supported

3. The diamond problem

Device is the parent of both Printer and Scanner; AllInOne inherits from both. If both override start(), which one does AllInOne get?

If Printer and Scanner both override start() and AllInOne inherits from both, which start() does it get? This ambiguity is the diamond problem.

  • Java avoids it by not allowing multiple class inheritance (if two interfaces have the same default method, you must override it).
  • Python resolves it with the MRO (Method Resolution Order): left to right, then upwards.
class Device:
    def start(self): return "Device"
class Printer(Device):
    def start(self): return "Printer"
class Scanner(Device):
    def start(self): return "Scanner"
class AllInOne(Printer, Scanner):
    pass

print(AllInOne().start())
print([c.__name__ for c in AllInOne.__mro__])
Printer
['AllInOne', 'Printer', 'Scanner', 'Device', 'object']

4. Composition over inheritance

Inheritance is powerful but creates tight coupling: change the parent and every child may break. Many designers prefer composition — build objects from other objects ("has-a"):

class Engine:
    def start(self): return "engine on"

class Car:
    def __init__(self):
        self.engine = Engine()          # Car HAS an Engine
    def drive(self):
        return self.engine.start() + ", driving"

print(Car().drive())
engine on, driving

Rule of thumb: use inheritance only for a true "is-a" relationship where the child can be used anywhere the parent is (see Liskov in the SOLID lesson). Otherwise, compose.

Common mistakes

  • Inheriting just to reuse one method ("Square extends Rectangle" problems).
  • Deep inheritance chains (5+ levels) that nobody can follow.
  • Forgetting to call the parent constructor (super().__init__).

Quick recap

  • Inheritance = "is-a"; the child reuses and may override the parent.
  • Types: single, multilevel, hierarchical, multiple, hybrid.
  • Diamond problem: Java blocks multiple class inheritance; Python uses the MRO.
  • Prefer composition ("has-a") when "is-a" isn't clearly true.

Practice what you learned

📝 Quiz: Inheritance5 questions · instant answers & explanations · +10 XP · counts towards your free certificateTake the quiz →