Skip to main content

👨‍👩‍👧‍👦 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!
Simple Definition: Inheritance = Child classes get everything from parent classes + can add their own stuff
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!

🔑 Key Concepts

1️⃣ The super() Function

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

2️⃣ Method Overriding

Children can replace parent methods with their own 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!)

🎮 Fun Example: RPG Character Classes

Let’s build a game with different character types:

📚 Types of Inheritance

Single Inheritance

One parent, one child

Multi-level Inheritance

Chain of inheritance

Multiple Inheritance

Multiple parents

Hierarchical Inheritance

One parent, many children

Multi-level Example

Multiple Inheritance Example


⚠️ When NOT to Use Inheritance

Don’t use inheritance just to share code! Use it when there’s a real IS-A relationship.
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

✅ Right: Use Composition Instead


🧪 Practice Exercise

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

📝 Quick Summary


Interview Insight

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.”

Interview Deep-Dive

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

🏃 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!

Continue to Polymorphism →

Learn how one interface can work with many different types - like a universal remote!