Join waitlist
OOPs ConceptsLesson 2 of 3

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.

7 min read 3 sections

01Where 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.
Inheritance forcing a class per combination
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.

02The composition version

Capabilities as separate contracts
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.

Choosing between them
QuestionInheritanceComposition
Relationshipis-ahas-a / can-do
BoundAt compile timeAt runtime, swappable
CouplingTight, to the parent's internalsLoose, to a contract
TestingNeeds the real parentInject a fake
Good forA genuine subtype with shared behaviourReusing behaviour across unrelated types
INHERITANCE — one parent, every capability baked in Bird fly() · swim() Sparrow Penguin Duck inherits fly() → must throw → a caller holding a Bird crashes COMPOSITION — capabilities as separate contracts Flies Swims Sparrow Duck Penguin
Inheritance forces a class per combination and leaves Penguin with a method it has to refuse — which is what breaks any caller holding a Bird.

03How 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; Flies and Swims instead of Bird.
  • 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

Test yourself on OOPs Concepts

Reading is not recall. Take a timed quiz on OOPs Concepts solo, or share a room code and battle friends on it.

More in OOPs Concepts