The idea
What it is
There are two main ways for one class to get behavior from another. Inheritance says a type is a kind of another type and reuses its code by extending it. Composition says a type has a helper object and reuses that object's behavior by holding it as a field and delegating to it. Same goal — reuse — but two very different shapes.
Picture a video-game character. With inheritance you'd try to bake every ability into the class tree: a FlyingCharacter, a SwimmingArmoredCharacter, and so on. With composition you keep one Character that simply holds a move-behavior, an attack-behavior, and a defense-behavior — small, swappable parts you snap in and out. Want a flying, laser-shooting, shielded character? Plug in those three behaviors. No new class required.
The one sentence to remember
Inheritance is is-a and locks the relationship in at design time; composition is has-a and lets you assemble — and even swap — behavior at runtime. When in doubt, favor composition.
Mechanics
How it works
The "is-a" vs "has-a" test
Before reaching for either tool, say the relationship out loud. If "a B is a A" sounds true — "a Dog is an Animal" — inheritance fits. If it sounds wrong but "a B has a A" is true — "a Character has a fly behavior" — that's composition. A character is not a kind of flying; it merely has a way to move. Getting this test right is the whole ballgame.
Why inheritance explodes
Inheritance forces every variation into the class tree. Suppose a character can move three ways (walk / fly / swim), attack two ways (sword / laser), and either have a shield or not. To cover every combination with subclasses you'd need one class per combination:
// one subclass for every combination of abilities…
3 (move) × 2 (attack) × 2 (defense) = 12 subclasses
// add a fourth move option and it jumps to 4 × 2 × 2 = 16
// add another axis (say 3 armor tiers) → 12 × 3 = 36This is the combinatorial explosion. Each new option multiplies the number of classes, and any shared logic gets copy-pasted across cousins in the tree. Worse, a tweak to a base class can quietly break dozens of descendants — the fragile base class problem.
How composition assembles small behaviors
Composition flips it. Instead of one class per combination, you write a handful of tiny behavior objects — FlyBehavior, SwimBehavior, LaserAttack, SwordAttack — each doing one thing. The Character just holds references to them and delegates: when asked to move, it calls this.moveBehavior.move(). Three move behaviors, two attack behaviors, and an optional shield cover all twelve combinations with only seven small objects, no subclass tree at all.
class Character {
constructor(
private moveBehavior: MoveBehavior, // HELD, not inherited
private attackBehavior: AttackBehavior,
) {}
performMove() { return this.moveBehavior.move(); } // delegate
performAttack() { return this.attackBehavior.attack(); }
// swap a behavior at RUNTIME — impossible with a fixed subclass
setMove(b: MoveBehavior) { this.moveBehavior = b; }
}Because the behavior lives in a field, you can change it while the program runs: hero.setMove(new FlyBehavior()) turns a walker into a flyer instantly. A subclass can never do that — its identity is fixed the moment it's constructed. (This swap-a-behavior-at-runtime idea is exactly the Strategy pattern, which you'll meet later — it's composition with a name.)
Favor composition over inheritance — and why
The famous guideline from the Gang of Four is "favor object composition over class inheritance." The reasons are concrete:
- Loose coupling — the
Characterdepends on small behavior interfaces, not on a sprawling parent's internals. Parts evolve independently. - No fragile base class — there's no deep tree where a base-class change ripples into every descendant.
- Runtime flexibility — behaviors are plugged in (and swapped) while the program runs, instead of being frozen at design time.
- No combinatorial explosion — new options add behaviors instead of multiplying classes.
"Favor" is not "always"
Composition is the default, not a law. Inheritance is still the right tool when there's a genuine is-a relationship with real shared behavior and you want polymorphism — SavingsAccount is a BankAccount. Reach for inheritance for honest hierarchies; reach for composition when you're really just assembling capabilities.
Interactive prototype
See it. Build it. Break it.
A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.
About this simulation
In the center sits one Character object. From the palette, plug in behaviors — pick how it moves, what it attacks with, whether it has a shield — and the abilities panel updates live. Press simulate turn to watch it act using whatever's currently plugged in. The counter on the right shows how many subclasses you'd need to cover every combination if you tried this with inheritance instead.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Plug in any combination
In the palette, change the Move dropdown to Fly, set Attack to Laser, and toggle Shield on. Watch the abilities panel under the character update instantly. You just built a flying, laser-shooting, shielded character without creating a single new class.
Simulate a turn
Press simulate turn. The console logs the character acting with whatever is plugged in right now — e.g. moves by flying, then attacks with laser, then raises shield. Change one dropdown and simulate again: only that part of the behavior changes, because each behavior is an independent object.
Watch the inheritance counter explode
Look at the "With inheritance you'd need…" panel on the right. As you select options it recomputes moves × attacks × defenses = N subclasses. Toggle + add an armor axis and watch the count multiply — the explosion composition quietly avoids with just a handful of behavior objects.
In practice
When to use it — and what trips people up
When inheritance is fine
Use inheritance when there's a genuine is-a relationship with real shared behavior, and you want polymorphism over the base type — a SavingsAccount is a BankAccount, a Circle is a Shape. The hierarchy is shallow, stable, and honest. If the is-a sentence rings true and you'd happily treat every subclass through the parent's interface, inheritance is the cleaner tool.
When to prefer composition
Prefer composition the moment you're reaching for inheritance just to reuse code, or when a type's capabilities vary along several independent axes (how it moves, attacks, defends). If you find yourself wanting to mix and match — or change behavior while the program runs — hold the behaviors as fields and delegate. That's the has-a shape, and it keeps the design flat, loosely coupled, and easy to extend.
What it gives you
- Loose coupling — the host depends on small behavior interfaces, not a parent's internals, so parts evolve independently.
- Runtime flexibility — behaviors are plugged in and can be swapped while the program runs (the basis of the Strategy pattern).
- No combinatorial explosion — new options add a behavior object instead of multiplying subclasses.
- Easier testing and reuse — each small behavior can be unit-tested and shared across many hosts.
Common mistakes
- Inheritance suffers a combinatorial explosion — one subclass per feature combination quickly becomes unmanageable.
- The fragile base class problem — a change high in the tree can silently break distant descendants.
- Inheriting purely for reuse couples unrelated types and often violates the Liskov Substitution Principle.
- Overusing composition adds its own noise — lots of tiny objects and wiring can obscure a simple, genuine is-a hierarchy.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// ❌ RIGID: a subclass per behavior combination
class Duck {
display() { return "a duck"; }
}
class FlyingDuck extends Duck {
fly() { return "flap flap"; }
}
class RubberDuck extends Duck {
// can't fly — but inherited fly() if Duck had it… messy overrides
quack() { return "squeak"; }
}
// add "rocket-powered" + "mute" and the tree multiplies…
// ✅ FLEXIBLE: Duck HOLDS pluggable behaviors
interface FlyBehavior { fly(): string; }
interface QuackBehavior { quack(): string; }
class FlyWithWings implements FlyBehavior { fly() { return "flap flap"; } }
class FlyNoWay implements FlyBehavior { fly() { return "can't fly"; } }
class LoudQuack implements QuackBehavior { quack() { return "QUACK"; } }
class MuteQuack implements QuackBehavior { quack() { return "..."; } }
class Duck2 {
constructor(
private fly: FlyBehavior,
private quack: QuackBehavior,
) {}
performFly() { return this.fly.fly(); } // delegate
performQuack() { return this.quack.quack(); }
setFly(b: FlyBehavior) { this.fly = b; } // swap at runtime!
}
const rubber = new Duck2(new FlyNoWay(), new MuteQuack());
const rocket = new Duck2(new FlyWithWings(), new LoudQuack());
rubber.setFly(new FlyWithWings()); // now the rubber duck can flyReferences & further reading
5 sources- Articleen.wikipedia.org
Wikipedia — Composition over inheritance
The canonical write-up of the principle, with the duck-behavior example and its rationale.
- Articleen.wikipedia.org
Wikipedia — Object composition
Background on has-a relationships, aggregation, and delegation between objects.
- Articlerefactoring.guru
Refactoring.Guru — Replace Inheritance with Delegation
A step-by-step refactoring that swaps a fragile subclass for a held, delegated object.
- Book
Design Patterns (GoF) — "favor object composition over class inheritance"
The Gang of Four book that coined the guideline and motivates it with the Strategy pattern.
- Book
Effective Java — Item 18: Favor composition over inheritance
Joshua Bloch's classic treatment of why inheritance across package boundaries is fragile.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Nothing is scored or saved.
question 01 / 04
Which relationship does composition model, and which does inheritance model?
question 02 / 04
A character can move 3 ways, attack 2 ways, and either carry a shield or not. With inheritance, how many subclasses cover every combination?
question 03 / 04
Why can a composed Character swap its move behavior at runtime, while a fixed subclass cannot?
question 04 / 04
Which statement best captures "favor composition over inheritance"?
0/4 answered