Many developers learn:
Inheritance allows code reuse.
Technically true.
But if that's your primary reason for using inheritance, you'll often create bad designs.
❓ Why Was Inheritance Invented?
Imagine:
User
├── Customer
├── Admin
└── Seller
All of them have:
- id
- name
- login()
- logout()
Instead of duplicating these everywhere:
class Customer { ... }
class Admin { ... }
class Seller { ... }
we extract common behavior:
class User {
id: string;
name: string;
email: string;
login() {}
logout() {}
}
Then:
class Customer extends User {}
class Admin extends User {}
class Seller extends User {}
This creates an IS-A relationship.
Admin IS A User
Customer IS A User
Seller IS A User
This is the most important rule of inheritance.
What Does extends Actually Do?
Consider:
class User {
login() {
console.log('login');
}
}
class Admin extends User {}
Now:
const admin = new Admin();
admin.login();
works.
Why?
Because JavaScript walks up the prototype chain:
admin
↓
Admin.prototype
↓
User.prototype
and finds login().
Inheritance is fundamentally prototype delegation.
User
↓
Admin
Admin has everything a User has, plus additional capabilities.
Example:
class User {
login() {}
}
class Admin extends User {
banUser() {}
}
Now:
admin.login();
admin.banUser();
Overriding Methods
Children can change behavior.
class User {
getRole() {
return'User';
}
}
class Admin extends User {
getRole() {
return'Admin';
}
}
Now:
newUser().getRole();
// User
newAdmin().getRole();
// Admin
Same method name.
Different behavior.
This becomes important later when we discuss polymorphism.
🤯 The Biggest Mistake with Inheritance
The Hidden Problem
Over time, developers started encountering a different challenge.
Imagine we have a Vehicle class:
class Vehicle {
start() {}
stop() {}
lockDoors() {}
}
Seems harmless.
But what happens when we introduce a Bicycle?
class Bicycle extends Vehicle {}
Bicycles don't have doors.
Yet they've inherited door-related behavior.
Now our model starts feeling strange.
The problem becomes even worse as systems grow.
Developers often found themselves inheriting behavior they didn't actually need.
Changes in parent classes unexpectedly affected child classes.
The hierarchy became increasingly difficult to maintain.
What started as a solution to code duplication sometimes created tightly coupled systems.
⚔️ The Double-Edged Sword
Inheritance solves one problem very well:
Reusing common behavior.
But it can introduce another:
Tight coupling between parent and child classes.
The child depends heavily on decisions made by the parent.
As hierarchies become deeper, understanding the system becomes harder.
A small change in a base class can ripple through multiple child classes.
This is one reason why modern software design is often more cautious about using inheritance.
✅ When Inheritance Is Actually Good
Use inheritance when:
1. Real IS-A relationship exists
Admin IS A User
Manager IS An Employee
Dog IS An Animal
2. Parent behavior is truly shared
class User {
login() {}
logout() {}
}
All child classes genuinely need this.
3. Hierarchy is shallow
Good:
User
├─ Admin
└─ Customer
Bad:
User
↓
Employee
↓
Manager
↓
RegionalManager
↓
DistrictManager
↓
SuperDistrictManager
⏭️ What's Next?
Inheritance helped developers reuse behavior.
But over time, many teams discovered that inheritance wasn't always the most flexible solution.
This led to an important shift in software design thinking:
Instead of inheriting behavior, what if we assembled behavior from smaller pieces?
In the next article, we'll explore Composition vs Inheritance and understand why many modern applications prefer composition when building complex systems.

