Composition Over Inheritance, With Examples
Inheritance is the first tool taught and the one most often misused. It couples a subclass to its parent's internals permanently, and that coupling is what makes old codebases rigid. Composition — holding an object instead of extending it — solves the same problems with far less commitment.
Where inheritance breaks
- Fixed at compile time. A subclass cannot change what it inherits while running; a composed dependency can be swapped.
- The fragile base class. Changing a parent method silently alters every subclass, including ones written by other teams.
- Combinatorial explosion. Three orthogonal traits force eight subclasses to cover the combinations.
- One parent only. In Java and C#, a class inherits from exactly one class, so the first inheritance decision spends the budget.
class Bird { void fly() {} void swim() {} }
class Penguin extends Bird {
void fly() { throw new UnsupportedOperationException(); } // a lie
}
Penguin is a bird but cannot fly, so it inherits a method it must refuse — and every caller holding a Bird can now crash. That is a Liskov violation, and the compiler will not catch it.
The composition version
interface Flies { void fly(); }
interface Swims { void swim(); }
class Sparrow implements Flies { public void fly() { /* ... */ } }
class Penguin implements Swims { public void swim() { /* ... */ } }
class Duck implements Flies, Swims { public void fly() { } public void swim() { } }
No class carries a method it cannot honour, capabilities combine freely, and a new one is a new interface rather than a rewrite of the hierarchy.
| Question | Inheritance | Composition |
|---|---|---|
| Relationship | is-a | has-a / can-do |
| Bound | At compile time | At runtime, swappable |
| Coupling | Tight, to the parent's internals | Loose, to a contract |
| Testing | Needs the real parent | Inject a fake |
| Good for | A genuine subtype with shared behaviour | Reusing behaviour across unrelated types |
The rule to state in an interview
Use inheritance only when the subclass is genuinely substitutable for the parent everywhere. If you are reaching for it just to reuse code, use composition — has-a beats is-a whenever both would work.
How this connects to SOLID
- S — Single responsibility. One reason to change per class; large inheritance trees usually violate it first.
- O — Open/closed. Open for extension, closed for modification — which is exactly what polymorphic dispatch buys.
- L — Liskov substitution. A subclass must work anywhere its parent does. The penguin fails this.
- I — Interface segregation. Many small contracts beat one large one;
FliesandSwimsinstead ofBird. - D — Dependency inversion. Depend on abstractions, not concrete classes — which is composition through an interface.
Key takeaways
- Inheritance is permanent, compile-time coupling to a parent's internals.
- If a subclass must refuse an inherited method, the hierarchy is wrong.
- Composition combines capabilities freely and can be swapped at runtime.
- Prefer inheritance only for genuine, fully substitutable subtypes.
Frequently asked questions
Why is composition preferred over inheritance?
Composition couples to a contract rather than to a parent's implementation, can be changed at runtime, avoids the fragile base class problem, and lets capabilities combine without a class per combination.
When is inheritance the right choice?
When the subclass genuinely is a kind of the parent and can be substituted for it everywhere, and when there is real shared state and behaviour to inherit rather than just code you want to reuse.
What is the fragile base class problem?
Changing a base class can silently break subclasses that depended on its internal behaviour, even though the subclasses were not modified and the change looked safe in isolation.
Test yourself on OOPs Concepts
Reading is not recall. Take a timed quiz on this topic solo, or share a room code and battle friends on it.
⚡ Start the OOPs Concepts quiz