Composition vs Inheritance
Inheritance is powerful, but it is not always the best design.
Classes give you more than one way to share behavior.
Two important approaches are:
- inheritance
- composition
Inheritance
Inheritance uses extends.
class Animal {
eat() {
return "Eating";
}
}
class Dog extends Animal {
bark() {
return "Woof";
}
}This says:
Dog is an Animal.Inheritance works best for true "is-a" relationships.
Composition
Composition means building an object by giving it other objects or functions to use.
class Engine {
start() {
return "Engine started";
}
}
class Car {
constructor(engine) {
this.engine = engine;
}
start() {
return this.engine.start();
}
}
const car = new Car(new Engine());
console.log(car.start()); // Engine startedThis says:
Car has an Engine.That is a "has-a" relationship.
Is-A vs Has-A
Use inheritance for "is-a".
Admin is a User.
Dog is an Animal.
SavingsAccount is a BankAccount.Use composition for "has-a" or "uses-a".
Car has an Engine.
UserService uses an ApiClient.
Checkout has a PaymentProcessor.This distinction helps you avoid forced inheritance.
Composition Example
class EmailSender {
send(to, message) {
return `Sending email to ${to}: ${message}`;
}
}
class UserNotifier {
constructor(sender) {
this.sender = sender;
}
notify(user, message) {
return this.sender.send(user.email, message);
}
}
const notifier = new UserNotifier(new EmailSender());
console.log(
notifier.notify({ email: "alice@example.com" }, "Welcome!")
);UserNotifier does not need to extend EmailSender.
It uses an EmailSender.
Why Composition Is Often Flexible
Composition lets you swap behavior.
class SmsSender {
send(to, message) {
return `Sending SMS to ${to}: ${message}`;
}
}
const smsNotifier = new UserNotifier(new SmsSender());UserNotifier can work with any object that has a compatible send method.
This can make code easier to test and change.
When Inheritance Gets Awkward
Imagine this:
Bird
FlyingBird
SwimmingBird
FlyingAndSwimmingBirdReal-world categories can get messy.
Some birds fly.
Some swim.
Some do both.
Some do neither.
Inheritance can create complicated class trees.
Composition can be simpler:
const canFly = {
fly() {
return "Flying";
},
};
const canSwim = {
swim() {
return "Swimming";
},
};You can combine behavior where needed.
Prefer Simple Designs
For beginner JavaScript, the best design is usually the clearest design.
Use a class when it helps model repeated objects with behavior.
Use inheritance when there is a clear parent-child relationship.
Use composition when an object uses another object to do part of its work.
Use plain functions when a class is unnecessary.
Classes With Modules
Classes are often exported from modules.
// User.js
export class User {
constructor(name) {
this.name = name;
}
}Import:
import { User } from "./User.js";
const user = new User("Alice");A class can also be a default export.
export default class User {}import User from "./User.js";Use the export style that matches your project.
Best Practices
Use inheritance only for meaningful "is-a" relationships.
Use composition for "has-a" and "uses-a" relationships.
Keep inheritance chains shallow.
Avoid designing class hierarchies before you need them.
Prefer simple functions or objects when they solve the problem clearly.
Use modules to keep classes focused and reusable.
Common Mistakes
Mistake 1: Using Inheritance Only to Reuse One Method
If you only need a helper function, export a function.
export function formatDate(date) {}Do not create a parent class just for unrelated utility sharing.
Mistake 2: Confusing Has-A With Is-A
Car is an EngineThis is wrong.
A car has an engine.
Composition is a better fit.
Mistake 3: Creating Deep Class Trees Too Early
Deep inheritance can become hard to change.
Start simple.
Add inheritance only when it makes the model clearer.
Summary
Inheritance and composition are two ways to organize behavior.
- Inheritance models "is-a" relationships.
- Composition models "has-a" or "uses-a" relationships.
extendsis useful, but should not be forced.- Composition is often more flexible.
- Plain functions and objects are sometimes simpler than classes.
- Keep class designs focused and easy to understand.