The is-a vs has-a decision
Inheritance shares implementation and establishes a subtype relationship. Composition embeds behavior and delegates to it. GoF's default advice — favor composition — exists because inheritance couples you to a parent's implementation details and freezes the hierarchy at compile time.
| Signal | Prefer inheritance | Prefer composition |
|---|---|---|
| Relationship | True, stable is-a (every SavingsAccount is an Account) | Behavior varies independently (Bird has-a FlyBehavior) |
| Extension axis | Fixed taxonomy, few variants | Open-ended variants (pricing, payment, eviction) |
| Runtime swap | Never needed | Needed (strategy swap, feature flags) |
| Depth | ≤ 2 levels | Any depth via delegation chains |
Fragile base class and the diamond problem
When a subclass overrides onUpdate() and the parent adds a new code path that forgets to call it, subclasses break without compiling. Composition avoids this: the wrapper controls all delegation. Multiple inheritance (or default interface methods) creates ambiguous resolution — another reason LLD interviews favor interfaces + composition over mixin towers.
Delegation pattern in practice
Replace class FlyingBird extends Bird with class Bird { private MovementBehavior movement; }. New behaviors = new classes, zero edits to Bird. Decorator and Strategy are composition dressed for specific jobs — wrapping and algorithm pluggability respectively.
When inheritance is still correct
Template Method (AbstractClass.process() calling abstract hook()) and true domain taxonomies (CheckingAccount extends Account with shared transfer()) are legitimate. The test: would a caller ever need to swap this behavior at runtime? If yes, composition.
Senior-level signal
When asked "why not inherit?", say: "Inheritance leaks the parent's contract — if PremiumMember extends Member but can't use borrow() the same way, LSP breaks. I'd model Member with a MembershipPolicy composed object instead." That sentence hits LSP, composition, and DIP in one breath.
Where this goes next
Classes, Interfaces & Cohesion turns these structural choices into crisp public APIs — how small to make classes, when to use abstract classes vs interfaces, and what callers actually see.
Further Reading
- Design Patterns (GoF) — Introduction §1.18: Favor object composition over inheritanceBook15m
- Effective Java, 3rd Ed — Item 18: Favor composition over inheritance (Item 19: design for inheritance or prohibit it)Book20m
- Refactoring.Guru — Composition vs Inheritance (when each is appropriate, with examples)Reference15m
Practice Tasks (Optional)
Design or implement locally in any language — no autograding. Focus on class structure, extensibility, and being able to explain trade-offs out loud.
- Refactor an inheritance hierarchy to composition20m
On paper: draw a 3-level inheritance tree (e.g. Bird → FlyingBird → Eagle). Redesign using a Bird class that composes a FlyBehavior interface. List what becomes easier to extend.