Skip to content
UniCircle
MediumConceptualDesign principles

Why prefer composition over inheritance?

Question OOP-106

A common design guideline says "favour composition over inheritance". What does this mean, what problems with inheritance does it address, and when is inheritance still the right choice?

Show hints

Compare an "is-a" relationship with a "has-a" relationship.

Show the explanation

Model answer.

Definitions

  • Inheritance ("is-a"): a subclass reuses and extends a parent class's implementation.
  • Composition ("has-a"): a class holds references to other objects and delegates work to them. A Car has an Engine; it is not an engine.

Problems with inheritance that composition avoids

  • Tight coupling / fragile base class: a subclass depends on the parent's internal behaviour. A change in the parent (say, one method starting to call another overridable method) can silently break subclasses.
  • Breaks encapsulation: subclasses often rely on protected internals instead of a public contract.
  • Inherits too much: the subclass gets every public method of the parent, even ones that make no sense. Java's own java.util.Stack extends Vector, so a stack exposes methods like insertElementAt that violate stack behaviour; the documentation now recommends Deque instead.
  • Rigid at run time: a class's parent is fixed at compile time.
  • Class explosion: combining features via subclasses (e.g. FlyingSwimmingDuck, FlyingNonSwimmingDuck, …) multiplies classes.

Benefits of composition

  • Behaviour can be swapped at run time by assigning a different component — the idea behind the Strategy pattern. For example, a PaymentService holding a PaymentGateway interface can switch between UPI and card implementations.
  • Components are smaller, independently testable, and easy to mock.
  • Only the methods you choose to delegate are exposed.

When inheritance is still right

  • A genuine, stable "is-a" relationship where the subclass can be used anywhere the parent is expected without surprises (Liskov Substitution Principle).
  • Frameworks designed for extension, e.g. extending an abstract base class with documented hook methods.
  • Implementing interfaces (inheritance of type) is generally fine; the guideline mainly warns against inheriting implementation just to reuse code.

Common follow-up: "Implement a counter that tracks how many items were added to a HashSet." Subclassing and overriding both add and addAll double-counts, because HashSet.addAll internally calls add. A wrapper class that holds a Set and forwards calls avoids depending on that internal detail.

Original UniCircle question. Last reviewed 28 Oct 2026.