Overview
Favor object composition over class inheritance.
Inheritance creates tight coupling and rigid hierarchies. Composition creates flexible, reusable components that can be mixed and matched.
When to Use
- Designing relationships between classes
- Tempted to use
extends - Class needs behavior from multiple sources
- Creating "is-a" relationships
- Building class hierarchies
The Iron Rule
NEVER use inheritance when composition would work.
No exceptions:
- Not for "it's the OOP way"
- Not for "is-a relationship"
- Not for "code reuse via extends"
- Not for "polymorphism"
Default to composition. Use inheritance only for true type hierarchies.
Detection: The Inheritance Smell
If inheritance feels awkward or forced, use composition:
// ❌ VIOLATION: Inheritance hierarchy
class Animal {
eat(): void { console.log('Eating'); }
}
class FlyingAnimal extends Animal {
fly(): void { console.log('Flying'); }
}
class SwimmingAnimal extends Animal {
swim(): void { console.log('Swimming'); }
}
// Duck needs both fly AND swim - inheritance can't do this cleanly
class Duck extends FlyingAnimal {
swim(): void { console.log('Swimming'); } // Duplicated!
}
Correct Pattern: Composition with Interfaces
Define capabilities as interfaces, compose them:
// ✅ CORRECT: Composition
interface Flyable {
fly(): void;
}
interface Swimmable {
swim(): void;
}
interface Eatable {
eat(): void;
}
// Reusable behaviors
const flyingBehavior: Flyable = {
fly() { console.log('Flying'); }
};
const swimmingBehavior: Swimmable = {
swim() { console.log('Swimming'); }
};
const eatingBehavior: Eatable = {
eat() { console.log('Eating'); }
};
// Compose what you need
class Duck implements Flyable, Swimmable, Eatable {
fly = flyingBehavior.fly;
swim = swimmingBehavior.swim;
eat = eatingBehavior.eat;
}
class Fish implements Swimmable, Eatable {
swim = swimmingBehavior.swim;
eat = eatingBehavior.eat;
}
class Bird implements Flyable, Eatable {
fly = flyingBehavior.fly;
eat = eatingBehavior.eat;
}
Why Inheritance Fails
| Problem | Example |
|---|---|
| Diamond problem | Duck needs Flying AND Swimming |
| Tight coupling | Child knows parent internals |
| Rigid hierarchy | Can't change parent without breaking children |
| Forced inheritance | Gets methods it doesn't need |
| Fragile base class | Parent changes break all children |
Why Composition Wins
| Benefit | Example |
|---|---|
| Flexible | Mix any behaviors together |
| Loose coupling | Components don't know each other |
| Easy testing | Mock individual behaviors |
| Runtime changes | Swap behaviors dynamically |
| No hierarchy lock-in | Add new combinations freely |
Pressure Resistance Protocol
1. "It's the OOP Way"
Pressure: "Object-oriented programming uses inheritance"
Response: Modern OOP favors composition. Inheritance is overused.
Action: Use interfaces + composition. It's still OOP.
2. "It's an Is-A Relationship"
Pressure: "A Duck IS-A Bird, so it should extend Bird"
Response: "Is-a" often becomes "has-a" when requirements change. Composition handles both.
Action: Model as "has behaviors" not "is a type".
3. "Code Reuse via Extends"
Pressure: "I need the parent's methods"
Response: Composition provides better code reuse without coupling.
Action: Extract shared behavior into composable units.
4. "Polymorphism Requires Inheritance"
Pressure: "I need to treat different types uniformly"
Response: Interfaces provide polymorphism without inheritance.
Action: Define interface, have classes implement it.
Red Flags - STOP and Reconsider
If you notice ANY of these, use composition instead:
extendskeyword in your code- Class hierarchy deeper than 2 levels
- Child class overriding parent methods
- "Diamond problem" - needs multiple parents
- Subclass doesn't use all parent methods
- Changing parent breaks children
- Hard to test without instantiating parent
All of these mean: Refactor to composition.
When Inheritance IS Appropriate
Use inheritance only when:
- True type hierarchy (rarely)
- Framework requires it (React class components, etc.)
- Extending library classes you don't control
Even then, keep hierarchy shallow (max 2 levels).
Quick Reference
| Inheritance | Composition |
|---|---|
class Dog extends Animal |
class Dog implements Animal + behavior injection |
| Rigid hierarchy | Flexible composition |
| Single parent only | Multiple behaviors |
| Tight coupling | Loose coupling |
| Changes cascade | Changes isolated |
Common Rationalizations (All Invalid)
| Excuse | Reality |
|---|---|
| "It's the OOP way" | Modern OOP prefers composition. |
| "It's an is-a relationship" | "Has behavior" is more flexible. |
| "Need parent's methods" | Compose the behavior instead. |
| "Polymorphism needs it" | Interfaces provide polymorphism. |
| "Less code with extends" | More flexibility with composition. |
| "I noted it's problematic" | Don't do it if it's problematic. |
The Bottom Line
Compose behaviors. Don't inherit them.
When designing classes: define interfaces for capabilities, create composable behaviors, inject what each class needs. Use extends only as last resort.