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

Source: https://skills101.in/learn/oops/oop-inheritance — Skills101 free tutorial (OOP Concepts). Updated 2026-11-09.

**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

```python
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))
```

```text
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](https://skills101.in/learn-img/oops/inheritance-types.svg)

| 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

![Device is the parent of both Printer and Scanner; AllInOne inherits from both. If both override start(), which one does AllInOne get?](https://skills101.in/learn-img/oops/diamond.svg)

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.

```python
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__])
```

```text
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"**):

```python
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())
```

```text
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 quiz and free certificate: https://skills101.in/learn/oops/oop-inheritance