> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Inheritance

> Pass down abilities from parent to child - like superpowers in a family!

## 👨‍👩‍👧‍👦 What is Inheritance?

Imagine a family where everyone has the same last name and some common traits:

* **Parents** have certain abilities (cooking, driving, etc.)
* **Children** automatically have those abilities too!
* But children can also have their **own special abilities**

**Inheritance** in OOP works the same way:

* A **Parent Class** (superclass) has some attributes and methods
* A **Child Class** (subclass) automatically gets all of those
* The child can add its **own special stuff** too!

<Tip>
  **Simple Definition**: Inheritance = Child classes get everything from parent classes + can add their own stuff
</Tip>

**Another analogy:** Think of a franchise restaurant like Subway. The corporate headquarters (parent class) defines the standard menu, processes, and branding. Each franchise location (child class) inherits all of that, but can add local specials or adapt to regional tastes. The franchise gets the proven foundation for free and customizes only what needs to be different.

***

## 🐾 The Classic Animal Example

Let's start with the most common example - Animals!

```python theme={null}
# 👆 PARENT CLASS (Superclass)
class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def eat(self):
        print(f"{self.name} is eating... 🍽️")
    
    def sleep(self):
        print(f"{self.name} is sleeping... 💤")
    
    def make_sound(self):
        print(f"{self.name} makes a sound")

# 👇 CHILD CLASSES (Subclasses) - inherit from Animal
class Dog(Animal):  # Dog IS-A Animal
    def __init__(self, name, age, breed):
        super().__init__(name, age)  # Call parent's __init__
        self.breed = breed  # Dog's own special attribute
    
    def make_sound(self):  # Override parent's method
        print(f"{self.name} says: Woof! Woof! 🐕")
    
    def fetch(self):  # Dog's own special method
        print(f"{self.name} is fetching the ball! 🎾")

class Cat(Animal):  # Cat IS-A Animal
    def __init__(self, name, age, color):
        super().__init__(name, age)
        self.color = color
    
    def make_sound(self):
        print(f"{self.name} says: Meow! 🐱")
    
    def climb(self):  # Cat's special method
        print(f"{self.name} climbed the tree! 🌳")

# Let's test it!
dog = Dog("Buddy", 3, "Golden Retriever")
cat = Cat("Whiskers", 2, "Orange")

# Both have parent's methods
dog.eat()         # Buddy is eating... 🍽️
cat.eat()         # Whiskers is eating... 🍽️
dog.sleep()       # Buddy is sleeping... 💤

# Each has their own sound
dog.make_sound()  # Buddy says: Woof! Woof! 🐕
cat.make_sound()  # Whiskers says: Meow! 🐱

# Each has special abilities
dog.fetch()       # Buddy is fetching the ball! 🎾
cat.climb()       # Whiskers climbed the tree! 🌳
```

***

## 🔑 Key Concepts

### 1️⃣ The `super()` Function

`super()` is like calling your parent to help you:

```python theme={null}
class Parent:
    def __init__(self, name):
        self.name = name
        print(f"Parent says: Hello, {name}!")

class Child(Parent):
    def __init__(self, name, toy):
        super().__init__(name)  # 📞 Call parent's __init__ first!
        self.toy = toy          # Then add child's own stuff
        print(f"Child says: I have a {toy}!")

kid = Child("Tommy", "robot")
# Output:
# Parent says: Hello, Tommy!
# Child says: I have a robot!
```

### 2️⃣ Method Overriding

Children can **replace** parent methods with their own version:

```python theme={null}
class Bird(Animal):
    def make_sound(self):
        print(f"{self.name} says: Tweet tweet! 🐦")
    
    # Override eat to be more specific
    def eat(self):
        print(f"{self.name} is pecking at seeds... 🌻")

sparrow = Bird("Sparky", 1)
sparrow.eat()  # Sparky is pecking at seeds... 🌻 (Bird's version)
```

### 3️⃣ The IS-A Relationship

Inheritance creates an "IS-A" relationship:

* Dog **IS-A** Animal ✅
* Cat **IS-A** Animal ✅
* Car IS-A Animal ❌ (doesn't make sense!)

```python theme={null}
dog = Dog("Rex", 5, "German Shepherd")
print(isinstance(dog, Dog))     # True
print(isinstance(dog, Animal))  # True - Dog IS-A Animal!
print(isinstance(dog, Cat))     # False - Dog is NOT a Cat
```

***

## 🎮 Fun Example: RPG Character Classes

Let's build a game with different character types:

```python theme={null}
class Character:
    """Base class for all game characters"""
    
    def __init__(self, name, health=100, mana=50):
        self.name = name
        self.health = health
        self.max_health = health
        self.mana = mana
        self.level = 1
    
    def take_damage(self, amount):
        self.health -= amount
        if self.health <= 0:
            self.health = 0
            print(f"💀 {self.name} has fallen!")
        else:
            print(f"😣 {self.name} took {amount} damage! HP: {self.health}/{self.max_health}")
    
    def heal(self, amount):
        self.health = min(self.health + amount, self.max_health)
        print(f"💚 {self.name} healed for {amount}! HP: {self.health}/{self.max_health}")
    
    def attack(self, target):
        damage = 10  # Base damage
        print(f"⚔️ {self.name} attacks {target.name}!")
        target.take_damage(damage)

# 🧙‍♂️ MAGE - Magic specialist
class Mage(Character):
    def __init__(self, name):
        super().__init__(name, health=80, mana=150)  # Less health, more mana
        self.spells = ["Fireball", "Ice Blast", "Lightning"]
    
    def cast_spell(self, spell_name, target):
        if spell_name not in self.spells:
            print(f"❌ {self.name} doesn't know {spell_name}!")
            return
        
        if self.mana < 20:
            print(f"❌ Not enough mana!")
            return
        
        self.mana -= 20
        damage = 30  # Magic is powerful!
        print(f"✨ {self.name} casts {spell_name}!")
        target.take_damage(damage)

# 🛡️ WARRIOR - Tank specialist
class Warrior(Character):
    def __init__(self, name):
        super().__init__(name, health=150, mana=20)  # More health, less mana
        self.armor = 10
        self.is_defending = False
    
    def take_damage(self, amount):  # Override to use armor
        if self.is_defending:
            amount = amount // 2
            print(f"🛡️ {self.name} blocks half the damage!")
            self.is_defending = False
        
        actual_damage = max(0, amount - self.armor)
        super().take_damage(actual_damage)  # Call parent's take_damage
    
    def defend(self):
        self.is_defending = True
        print(f"🛡️ {self.name} raises their shield!")
    
    def heavy_strike(self, target):
        print(f"💪 {self.name} uses HEAVY STRIKE!")
        target.take_damage(25)

# 🏹 ARCHER - Range specialist  
class Archer(Character):
    def __init__(self, name):
        super().__init__(name, health=90, mana=60)
        self.arrows = 20
    
    def shoot_arrow(self, target):
        if self.arrows <= 0:
            print(f"❌ {self.name} is out of arrows!")
            return
        
        self.arrows -= 1
        print(f"🏹 {self.name} shoots an arrow! (Arrows left: {self.arrows})")
        target.take_damage(18)
    
    def multi_shot(self, targets):
        """Shoot 3 arrows at multiple targets!"""
        if self.arrows < 3:
            print(f"❌ Need 3 arrows for multi-shot!")
            return
        
        self.arrows -= 3
        print(f"🏹🏹🏹 {self.name} uses MULTI-SHOT!")
        for target in targets[:3]:
            target.take_damage(12)

# 🎮 Let's play!
mage = Mage("Gandalf")
warrior = Warrior("Conan")
archer = Archer("Legolas")

print("=== Battle Begins! ===\n")

# Mage attacks warrior
mage.cast_spell("Fireball", warrior)

# Warrior defends then attacks
warrior.defend()
mage.attack(warrior)  # Will be blocked

# Warrior counter attacks
warrior.heavy_strike(mage)

# Archer joins the fight
archer.shoot_arrow(mage)

print(f"\n=== Final Status ===")
print(f"🧙‍♂️ {mage.name}: {mage.health} HP, {mage.mana} Mana")
print(f"🛡️ {warrior.name}: {warrior.health} HP")
print(f"🏹 {archer.name}: {archer.health} HP, {archer.arrows} Arrows")
```

***

## 📚 Types of Inheritance

<CardGroup cols={2}>
  <Card title="Single Inheritance" icon="1">
    One parent, one child

    ```
    Animal
       ↓
      Dog
    ```
  </Card>

  <Card title="Multi-level Inheritance" icon="stairs">
    Chain of inheritance

    ```
    Animal
       ↓
    Mammal
       ↓
      Dog
    ```
  </Card>

  <Card title="Multiple Inheritance" icon="code-merge">
    Multiple parents

    ```
    Flyable  Swimmable
        ↘    ↙
         Duck
    ```
  </Card>

  <Card title="Hierarchical Inheritance" icon="sitemap">
    One parent, many children

    ```
        Animal
       /   |   \
    Dog   Cat   Bird
    ```
  </Card>
</CardGroup>

### Multi-level Example

```python theme={null}
class Animal:
    def breathe(self):
        print("Breathing... 💨")

class Mammal(Animal):  # Inherits from Animal
    def feed_milk(self):
        print("Feeding milk to babies 🍼")

class Dog(Mammal):  # Inherits from Mammal (and thus Animal)
    def bark(self):
        print("Woof! 🐕")

dog = Dog()
dog.breathe()    # From Animal ✅
dog.feed_milk()  # From Mammal ✅
dog.bark()       # From Dog ✅
```

### Multiple Inheritance Example

```python theme={null}
class Flyable:
    def fly(self):
        print("Flying through the sky! 🦅")

class Swimmable:
    def swim(self):
        print("Swimming in water! 🏊")

class Duck(Flyable, Swimmable):  # Inherits from BOTH
    def quack(self):
        print("Quack quack! 🦆")

duck = Duck()
duck.fly()    # From Flyable ✅
duck.swim()   # From Swimmable ✅
duck.quack()  # From Duck ✅

print("A duck can do everything!")
```

***

## ⚠️ When NOT to Use Inheritance

<Warning>
  Don't use inheritance just to share code! Use it when there's a real IS-A relationship.
</Warning>

**Design reasoning:** This is one of the most important judgment calls in OOP. Inheritance creates a *permanent, structural* relationship -- the child is forever coupled to the parent. If the parent changes, every child changes too. The "IS-A" test is your guardrail: use inheritance only when the child truly *is a kind of* the parent in every meaningful context. When you just want to reuse some behavior, prefer composition ("HAS-A") instead. A senior engineer would say: "Inheritance is a power tool -- it can build things fast, but it can also cut you if used carelessly. Default to composition and reach for inheritance only when the IS-A relationship is genuinely true."

### ❌ Wrong: Stack inherits from List

```python theme={null}
# BAD: A Stack IS-NOT-A List (even if they share some behavior)
class Stack(list):  # Don't do this!
    def push(self, item):
        self.append(item)
    
    def pop(self):
        return super().pop()

# Problem: Stack now has all list methods!
stack = Stack()
stack.push(1)
stack.push(2)
stack.insert(0, 100)  # 😱 Users can break the stack behavior!
```

### ✅ Right: Use Composition Instead

```python theme={null}
# GOOD: Stack HAS-A list inside (composition)
# PRINCIPLE: Composition over inheritance -- the stack *uses* a list
# internally but does not *expose* list behavior to callers.
class Stack:
    def __init__(self):
        self._items = []  # Stack HAS a list (not IS-A list)
    
    def push(self, item):
        self._items.append(item)
    
    def pop(self):
        return self._items.pop()
    
    def peek(self):
        return self._items[-1] if self._items else None
    
    def is_empty(self):
        return len(self._items) == 0

# Now users can only use stack operations!
stack = Stack()
stack.push(1)
stack.push(2)
# stack.insert(0, 100)  # Error! No insert method available
# This is the benefit: the public interface is exactly what a stack should be.
```

***

## 🧪 Practice Exercise

<Accordion title="Challenge: Build a Vehicle Hierarchy" icon="car">
  Create a vehicle inheritance system:

  1. **Vehicle** (parent): has brand, model, and a `start()` method
  2. **Car** (child): adds number of doors, and `honk()` method
  3. **Motorcycle** (child): adds engine\_cc, and `wheelie()` method
  4. **ElectricCar** (child of Car): adds battery\_capacity, and `charge()` method

  ```python theme={null}
  class Vehicle:
      def __init__(self, brand, model):
          # TODO: Initialize brand and model
          pass
      
      def start(self):
          # TODO: Print starting message
          pass

  class Car(Vehicle):
      # TODO: Add doors, override start to say "Car starting..."
      pass

  class Motorcycle(Vehicle):
      # TODO: Add engine_cc, add wheelie() method
      pass

  class ElectricCar(Car):
      # TODO: Add battery_capacity, add charge() method
      pass

  # Test
  tesla = ElectricCar("Tesla", "Model 3", 4, 75)
  tesla.start()    # Should work (from Car/Vehicle)
  tesla.honk()     # Should work (from Car)
  tesla.charge()   # Should work (from ElectricCar)
  ```

  <details>
    <summary>Click for Solution</summary>

    ```python theme={null}
    class Vehicle:
        def __init__(self, brand, model):
            self.brand = brand
            self.model = model
        
        def start(self):
            print(f"🔑 {self.brand} {self.model} is starting...")

    class Car(Vehicle):
        def __init__(self, brand, model, doors):
            super().__init__(brand, model)
            self.doors = doors
        
        def start(self):
            print(f"🚗 Car starting... {self.brand} {self.model}")
        
        def honk(self):
            print("📯 BEEP BEEP!")

    class Motorcycle(Vehicle):
        def __init__(self, brand, model, engine_cc):
            super().__init__(brand, model)
            self.engine_cc = engine_cc
        
        def start(self):
            print(f"🏍️ VROOM! {self.brand} {self.model} ({self.engine_cc}cc)")
        
        def wheelie(self):
            print("🎪 Doing a wheelie!")

    class ElectricCar(Car):
        def __init__(self, brand, model, doors, battery_kwh):
            super().__init__(brand, model, doors)
            self.battery_kwh = battery_kwh
        
        def start(self):
            print(f"⚡ {self.brand} {self.model} silently starting...")
        
        def charge(self):
            print(f"🔋 Charging {self.battery_kwh}kWh battery...")

    # Test all vehicles
    print("=== Testing Vehicles ===\n")

    car = Car("Toyota", "Camry", 4)
    car.start()
    car.honk()

    print()

    bike = Motorcycle("Harley", "Sportster", 1200)
    bike.start()
    bike.wheelie()

    print()

    tesla = ElectricCar("Tesla", "Model 3", 4, 75)
    tesla.start()
    tesla.honk()
    tesla.charge()
    ```
  </details>
</Accordion>

***

## 📝 Quick Summary

| Concept          | Description                    | Example                          |
| ---------------- | ------------------------------ | -------------------------------- |
| **Parent Class** | The class being inherited from | `Animal`                         |
| **Child Class**  | The class that inherits        | `Dog(Animal)`                    |
| **super()**      | Calls parent's methods         | `super().__init__()`             |
| **Override**     | Replace parent's method        | Redefine `make_sound()` in child |
| **IS-A**         | The relationship test          | Dog IS-A Animal ✅                |

***

## Interview Insight

<Info>
  **Common interview trap:** Candidates overuse inheritance in their LLD designs. When designing a system, interviewers watch for whether you default to deep class hierarchies or use composition wisely. For example, if asked to design a notification system, a weak answer creates `EmailNotification extends Notification extends Message extends BaseEntity` -- four levels deep. A strong answer uses composition: a `Notification` class *has* a `Channel` (email, SMS, push) and a `Template`. The moment you say "I'll prefer composition here because these components change independently," you signal senior-level thinking. Remember: favor shallow hierarchies (1-2 levels) and reach for composition when the relationship is "uses" rather than "is a kind of."
</Info>

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="An interviewer says: 'The Gang of Four says favor composition over inheritance. Does that mean inheritance is always wrong?' Defend your position.">
    **Strong Answer:**

    * Inheritance is correct when three conditions are met: the relationship is genuinely "is-a" and will remain stable, the base class is unlikely to change its contract, and you need polymorphic substitution. The Animal/Dog/Cat hierarchy is valid -- a Dog genuinely is an Animal, and the Animal contract is stable.
    * Inheritance is wrong when any of these fail. Stack-extends-List exposes methods (insert, sort) that violate stack semantics. Composition (Stack has-a list internally) gives full control over the exposed interface.
    * The deeper issue is coupling. Inheritance is the tightest coupling in OOP -- the child depends on the parent's implementation, not just its interface. Composition couples you only to the interface.

    **Follow-up: In the RPG example, Warrior overrides take\_damage(). What if a new class is inserted between Character and Warrior in the hierarchy?**

    This is the fragile base class problem. The super() call in Warrior dispatches according to the MRO, which changes when a new class is inserted. The new class's take\_damage() may double-apply armor or conflict with the defend logic. The fix: keep hierarchies shallow (two levels max) or use composition with a DamageHandler strategy.
  </Accordion>

  <Accordion title="Explain Python's MRO and the diamond problem. Why does this matter for LLD design?">
    **Strong Answer:**

    * The diamond problem occurs when Duck inherits from FlyingBird and SwimmingBird, which both inherit from Bird. Python solves this with C3 linearization -- a deterministic ordering where each class is visited exactly once. You can inspect it with Duck.**mro**.
    * For LLD, the practical implication is: use multiple inheritance sparingly and only for orthogonal capabilities (Flyable and Swimmable). Use ABCs as "interfaces" (no shared state) for safe multiple inheritance.
    * When in doubt, prefer composition. A Warrior's damage reduction might change at runtime (different armor), so composition with a DamageStrategy is better than an ArmorMixin.

    **Follow-up: When would you use a mixin versus composition?**

    Mixins work when the behavior is orthogonal, stateless, and will not vary at runtime (LoggingMixin, SerializableMixin). If the behavior carries state or might change dynamically, use composition. The test: "Would I ever want to swap this behavior at runtime?" If yes, composition. If no, mixin is acceptable.
  </Accordion>

  <Accordion title="The Vehicle exercise has three levels of inheritance: Vehicle, Car, ElectricCar. Is that too deep? How do you decide?">
    **Strong Answer:**

    * Three levels is at the boundary. It works here because each level adds genuine specialization and the hierarchy is stable. But I would question whether the third level is necessary. If the only difference is the power source, composition might be cleaner: Car has-a Powertrain interface (Gas, Electric, Hybrid). Adding hydrogen fuel cell does not require a new hierarchy branch.
    * My rule: two levels is always fine, three requires justification, four or more is almost certainly wrong. In an interview, I present the inheritance version first (faster to draw) then offer the composition alternative. This shows I know both and can judge which fits.

    **Follow-up: What is the LSP concern with ElectricCar overriding Car.start()?**

    If consumer code checks the output of start() and expects "engine starting," ElectricCar's "silently starting" breaks the contract. The fix: define start() at the Vehicle level as "prepare for operation" without engine-specific language. Subtypes implement their own version, and consumers rely on the abstract contract.
  </Accordion>
</AccordionGroup>

***

## 🏃 Next Up: Polymorphism

Now that you can create family trees of classes, let's learn how **the same method can behave differently** for different objects!

<Card title="Continue to Polymorphism →" icon="shapes" href="/lld/oop/polymorphism">
  Learn how one interface can work with many different types - like a universal remote!
</Card>
