Composition over Inheritance
“Favour composition over inheritance” is one of the most quoted design rules. Instead of inheriting behaviour from a parent, an object holds other objects that do the work — and you can swap them freely.
On this page · 6 min · Intermediate
On this page
- In simple words
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Intermediate
- Updated
- Oct 28, 2026
Related tutorials
In simple words#
Build with LEGO pieces instead of carving one statue. Combining small parts (composition) is easier to change than a deep family tree (inheritance).
- Composition
- Build objects from other objects.
- Deep inheritance
- Long parent chains that are hard to change.
- Flexibility
- Swap parts at runtime.
Learning objectives#
- Explain the class-explosion problem with inheritance
- Build behaviour by combining objects
- Swap behaviour at runtime
- Know when inheritance is still the right tool
Explanation#
Inheritance fixes behaviour at compile time and supports only one parent. Combine two features — petrol or electric, with or without GPS — and you need PetrolCar, ElectricCar, PetrolCarWithGps, ElectricCarWithGps. Each new feature doubles the classes.
Composition gives the car a powerSource object and an optional gps object. Any power source and any GPS can be combined — two small classes per feature, not a multiplying tree.
Composed parts can be replaced at runtime (fit a GPS later) and tested on their own.
Changes to a base class ripple into every subclass (the “fragile base class” problem). Composed parts only interact through small interfaces, so changes stay local.
Inheritance is still right for true, stable is-a relationships with shared behaviour — a SavingsAccount is an Account. Use composition for “has a” and “can do”.
Syntax#
public interface IPowerSource { string Start(); }
public interface INavigator { string Route(string to); }
public class Car(string name, IPowerSource power, INavigator? gps = null)
{
public string Start() => quot;{name}: {power.Start()}";
public string Navigate(string to) => gps?.Route(to) ?? "no GPS fitted";
}
Code example: Cars built from parts#
// Inheritance would need: PetrolCar, ElectricCar, PetrolCarWithGps, ElectricCarWithGps...
// Composition: small parts, combined freely.
class PetrolEngine {
constructor(hp) { this.hp = hp; }
start() { return `engine ${this.hp}hp started`; }
}
class ElectricMotor {
constructor(kw) { this.kw = kw; }
start() { return `motor ${this.kw}kW humming`; }
}
class Gps {
route(to) { return `route to ${to}`; }
}
class Car {
constructor(name, powerSource, gps = null) {
this.name = name;
this.power = powerSource; // has-a power source
this.gps = gps; // has-a (optional) GPS
}
start() { return `${this.name}: ${this.power.start()}`; }
navigate(to) { return this.gps ? this.gps.route(to) : 'no GPS fitted'; }
}
const city = new Car('City', new PetrolEngine(120), new Gps());
const nexon = new Car('Nexon EV', new ElectricMotor(105));
console.log(city.start());
console.log(nexon.start());
console.log(city.navigate('Pune'));
console.log(nexon.navigate('Pune'));
nexon.gps = new Gps(); // fitted later, at runtime
console.log(nexon.navigate('Mumbai'));
City: engine 120hp started Nexon EV: motor 105kW humming route to Pune no GPS fitted route to Mumbai
Step-by-step explanation#
- PetrolEngine and ElectricMotor both have start(), so the car can use either.
- Car does not inherit from an engine; it holds one and delegates to it.
- The optional GPS is another part. No GPS → a sensible message instead of a new class.
- Fitting a GPS later is just assigning a new part — impossible with inheritance, where the class is fixed.
Real-world use case#
ASP.NET Core services are composed: an OrderService has an IOrderRepository, an IPaymentGateway and an ILogger. Swapping SQL Server for Cosmos DB means injecting a different repository, not rewriting a class hierarchy.
Common mistakes#
class Report extends Logger to get log() is wrong — a report is not a logger. Give the report a logger instead.
Five levels of subclasses make every change risky. Flatten them and move varying behaviour into composed parts.
Composition everywhere can create many tiny classes for no reason. Use inheritance when the is-a relationship is real and stable.
Best practices#
- Ask “is-a or has-a?” before writing extends
- Put varying behaviour into small, swappable parts
- Depend on interfaces for those parts
- Keep inheritance hierarchies shallow (1–2 levels)
Remember this#
Has-a beats is-a when behaviour varies.
Inheritance multiplies classes; composition adds them.
Parts can be swapped at runtime; parents cannot.
Practice exercise#
Your task: Build a Character that has a weapon part. Create Sword (attack returns "slashes for 10") and Bow (attack returns "shoots for 7"). Make a character, attack, switch weapon, attack again.
Need a hint?
Show solution
class Sword { attack() { return 'slashes for 10'; } }
class Bow { attack() { return 'shoots for 7'; } }
class Character {
constructor(name, weapon) { this.name = name; this.weapon = weapon; }
attack() { return `${this.name} ${this.weapon.attack()}`; }
}
const hero = new Character('Arjun', new Sword());
console.log(hero.attack()); // Arjun slashes for 10
hero.weapon = new Bow();
console.log(hero.attack()); // Arjun shoots for 7
Mini quiz#
What problem does composition solve that inheritance creates?
Want more? Practise all OOP Concepts MCQs · OOP Concepts interview questions
My notes#
Saved in this browser.