Back to the path
Level 0 beginner 22 min #oop#fundamentals#design

Composition vs Inheritance

The single most repeated piece of LLD advice — 'favor composition over inheritance' — and exactly when each one is right.

The most-repeated advice in design

You will hear “favor composition over inheritance” in nearly every LLD discussion. It’s good advice — but as a default, not a law. Let’s make it concrete.

Two ways to reuse behavior

Inheritance (“is-a”) — a Dog is an Animal, so it inherits Animal’s behavior:

class Animal { breathe() {} }
class Dog extends Animal { bark() {} }

Composition (“has-a”) — a Car has an Engine; it holds a reference and delegates:

class Engine { start() {} }
class Car {
private engine = new Engine();
start() { this.engine.start(); }   // delegate
}

Why composition is usually the safer default

The class-explosion problem

Imagine characters with abilities: walk, fly, swim. With inheritance you end up writing WalkingFlyingCharacter, WalkingSwimmingCharacter, FlyingSwimmingCharacter… every combination becomes a class. With N abilities that’s up to 2ᴺ classes. With composition, you attach the abilities you want to one Character.

Runtime flexibility

Inheritance is fixed at compile time — a Dog can’t stop being an Animal. Composition can change at runtime: a power-up adds a Fly behavior to an existing character. This is exactly what the Strategy pattern formalizes (you’ll meet it in Level 3).

interface MoveBehavior { move(): void; }
class Walk implements MoveBehavior { move() {} }
class Fly  implements MoveBehavior { move() {} }

class Character {
private abilities: MoveBehavior[] = [];
addAbility(a: MoveBehavior) { this.abilities.push(a); }  // power-up at runtime
act() { this.abilities.forEach(a => a.move()); }
}

The fragile base class problem

When many subclasses inherit from one base, a small change to the base can break them in surprising ways. Composition keeps each part independent and separately testable.

So when should you use inheritance?

Inheritance earns its place when all of these hold:

  1. There’s a genuine “is-a” relationship — not just “I want to reuse this code.”
  2. The subtype is truly substitutable for the supertype (this is the Liskov Substitution Principle — Level 2).
  3. The hierarchy is stable and shallow.

A Square that is genuinely a Shape, a SavingsAccount that is genuinely a BankAccount — fine. But “InvoiceService extends DatabaseConnection so it can reuse the query method” is a misuse: that’s a has-a, model it with composition.

The mental test

Before you type extends, ask: “Is this a true is-a, or am I just borrowing code?” If it’s the latter, hold an instance instead of inheriting from it.

Tip

In an interview, narrate this tradeoff out loud: “I’ll compose these behaviors rather than inherit, so abilities can change at runtime and I avoid a class explosion.” Interviewers score you on reasoning, not just the final diagram.

The design problem below is the classic that trips up juniors — work it before revealing the reference.

Assessment

Pass mark: 70% on the concept questions unlocks the next lesson.

1. 'Favor composition over inheritance' primarily helps you avoid:

2. Which relationship best signals you should use INHERITANCE?

3. Benefits of composition over inheritance include: (select all) (select all)

Design problem 4

You're modeling game characters. Some can walk, some fly, some swim, and abilities change during play (a power-up grants flight). A junior dev proposes `class FlyingSwimmingWalkingCharacter extends ...`. Redesign this. Show your approach.