Inheritance
Inheritance is the OOP pillar most abused by beginners and most scrutinized by interviewers. Knowing when to inherit — and when to prefer composition — is what separates "I know the syntax" from "I can design."
The core idea
Inheritance lets a class (the subclass/derived/child) acquire the fields and methods of another class (the superclass/base/parent), then extend or override them. It models an is-a relationship: a Dog is an Animal, so Dog inherits from Animal. The goal is code reuse and building specialized types from general ones.
Beginner — extend and override
A subclass inherits the parent's members and can:
- add new fields/methods,
- override inherited methods to change behavior (e.g., Dog overrides Animal's speak() to bark),
- call the parent's version via super.
A constructor in the subclass typically calls the parent constructor first to initialize inherited state.
Intermediate — single vs multiple inheritance
Some languages allow only single inheritance of classes (Java, C#) to avoid ambiguity, while others allow multiple inheritance (C++). The classic problem with multiple inheritance is the diamond problem: if B and C both inherit from A and D inherits from both, which A does D get? Languages solve this via interfaces (Java), virtual inheritance (C++), or MRO rules (Python).
Advanced — composition over inheritance
Inheritance is tight coupling: subclasses depend on the parent's internals, and deep hierarchies become fragile (a change in the base ripples everywhere — the "fragile base class" problem). Misusing is-a where the relationship is really has-a breaks the Liskov Substitution Principle (a subclass must be usable anywhere its parent is). The widely-taught guidance is "favor composition over inheritance": instead of inheriting, hold a reference to a helper object and delegate. Use inheritance only for genuine is-a specialization; use composition for has-a and for combining behaviors flexibly.
Worked example
Animal defines speak() and eat(). Dog extends Animal, inherits eat(), and overrides speak() to return "Woof". Cat extends Animal and overrides speak() to "Meow". Code that holds an Animal can call speak() on any of them. But if you tried to make a Stack "inherit" from a List just to reuse its methods, you'd expose insert-anywhere operations that violate stack semantics — that's a case for composition (Stack has-a List) instead.
When to use
Use inheritance when there's a true is-a relationship and subclasses are proper substitutes for the base. Prefer composition when you're reusing code without an is-a bond, when you'd otherwise build a deep/rigid hierarchy, or when behaviors should be mixed and matched at runtime.
Tricks and mnemonic
Remember "inherit for is-a, compose for has-a." If you can't say "the child IS A kind of the parent" truthfully, don't inherit.
Don't inherit just to reuse a few methods. Reaching for inheritance where the relationship isn't truly is-a creates fragile, tightly coupled hierarchies and can violate the Liskov Substitution Principle. Prefer composition (has-a) for pure code reuse.
- ✓- Inheritance = a subclass acquires and extends/overrides a superclass (is-a).
- ✓- Subclasses add members, override methods, and call super for the parent version.
- ✓- Single inheritance (Java/C#) vs multiple (C++); the diamond problem is the classic pitfall.
- ✓- Deep hierarchies are fragile and tightly coupled (fragile base class).
- ✓- Favor composition (has-a) over inheritance for reuse without a true is-a bond.
- ✓Inheritance derives a specialized child from a parent to reuse and extend behavior, modeling is-a. It enables overriding and polymorphism but couples classes tightly and can grow fragile hierarchies. Inherit only for genuine is-a substitutability; otherwise favor composition (has-a) for flexible reuse.
Inheritance — Revision Notes
Fast revision of inheritance.
- Inheritance: a subclass acquires a superclass's fields/methods and extends or overrides them; models an is-a relationship.
- Subclasses can add members, override methods, and call the parent via super; constructors chain to the parent.
- Single inheritance (Java, C#) vs multiple inheritance (C++); the diamond problem is the classic ambiguity.
- Inheritance is tight coupling; deep hierarchies are fragile (fragile base class problem).
- Misusing is-a where it's really has-a can violate the Liskov Substitution Principle.
- Guidance: favor composition over inheritance — hold a helper object and delegate for pure code reuse.
- Rule: inherit for is-a, compose for has-a.
Inheritance — Flashcards
Cover the answer, recall, then check. 8 cards.
Q1. What relationship does inheritance model?
A1. An is-a relationship (a Dog is an Animal) for reuse and specialization.
Q2. What does overriding a method do?
A2. Replaces an inherited method's behavior in the subclass (e.g., Dog's speak() barks).
Q3. What does the super keyword do?
A3. Calls the parent class's version of a method or constructor.
Q4. Single vs multiple inheritance?
A4. Single allows one parent class (Java/C#); multiple allows several (C++), risking ambiguity.
Q5. What is the diamond problem?
A5. With multiple inheritance, if two parents share a common ancestor, it's ambiguous which inherited version a class gets.
Q6. What is the fragile base class problem?
A6. Changes to a superclass can unexpectedly break subclasses that depend on its internals.
Q7. State the "favor composition over inheritance" guidance.
A7. For code reuse without a true is-a bond, hold and delegate to a helper object (has-a) instead of inheriting.
Q8. Quick rule for choosing inheritance vs composition?
A8. Inherit for is-a; compose for has-a.
Inheritance — Worked Example
Worked Example
Problem: Given the classes below, what do d.eat() and d.sound() print, and why?
class Animal {
void eat() { print("eating"); }
String sound() { return "..."; }
}
class Dog extends Animal {
String sound() { return "Woof"; } // override
}
Dog d = new Dog();
Solution: Inheritance lets Dog reuse Animal's members (an "is-a" relationship: a Dog is an Animal) and override the ones it wants to specialise.
d.eat()— Dog does not defineeat, so the inheritedAnimal.eat()runs → prints "eating".d.sound()— Dog overridessound(), so its version runs → returns "Woof" (not "...").
Inheritance avoids duplicating shared behaviour (eat) while allowing subclasses to customise specific behaviour (sound).
Answer: d.eat() prints "eating" (inherited); d.sound() returns "Woof" (overridden).
- ✓- Inheritance models "is-a" and reuses base-class members in subclasses.
- ✓- A subclass inherits methods it doesn't define and can override those it does.
- ✓- Favour composition over deep inheritance when the relationship isn't truly "is-a".