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
The same character, built two ways, side by side. Up top, describe the character you want — moves by, attacks with, defense. The left Inheritance (is-a) panel shows a zoo of named subclasses — one per combination — with your current pick highlighted (e.g. FlyLaserShieldHero) and a live count of how many classes you'd have to write. The right Composition (has-a) panel keeps just one Character that holds swappable parts. Flip + armor tier (×3) and watch the inheritance zoo multiply while composition only adds one slot. Hit Run this character to see the single object act with its plugged-in parts.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Find your character in the inheritance zoo
In the top bar set moves by Fly, attacks with Laser, defense Shield. In the left Inheritance (is-a) panel one chip lights up — FlyLaserShieldHero — the exact subclass you'd have to write. Every other chip in that box is another class you'd also need; the subclasses to write & maintain counter shows how big that zoo already is.
Watch composition stay at one object
Now look at the right Composition (has-a) panel. The same choices just snapped three small parts — FlyBehavior, LaserAttack, ShieldBehavior — into the single hero : Character's slots. No new class. Change attacks with to Sword: only that one slot swaps, while the left panel jumps to a completely different class.
Add an axis and watch the explosion
Flip + armor tier (×3) on. The inheritance zoo visibly multiplies — every class spawns three (…LightHero, …MediumHero, …HeavyHero) and the counter jumps from 18 to 54. Composition just adds one armor slot and three reusable parts — +1, not ×3. That widening gap is the whole reason to favor composition. Press Run this character to see the one object act with its current parts.
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. Sign in to save your best score.
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