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
| Type | Shape | Supported in |
|---|---|---|
| Single | A → B | all |
| Multilevel | A → B → C | all |
| Hierarchical | A → B, A → C | all |
| Multiple | A + B → C | Python, C++ (Java/C# only via interfaces) |
| Hybrid | a mix of the above | where multiple is supported |
3. The diamond problem
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.