Class Inheritance in JavaScript: Complete Guide
Class inheritance lets you build new classes on top of existing ones. Learn extends, super, method overriding, and how JavaScript classes chain prototypes under the hood.
JavaScript class inheritance lets you build a new class on top of an existing one. The child class, also called a subclass, inherits properties and methods from the parent class, also called a superclass. This lets you reuse and extend behavior without rewriting code from scratch.
class Animal {
constructor(name) {
this.name = name;
}
speak() { return `${this.name} makes a sound.`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks.`; }
}
const rex = new Dog("Rex");
console.log(rex.speak()); // "Rex barks."
console.log(rex.name); // "Rex", inherited from Animal's constructorDog extends Animal means Dog inherits everything from Animal. The name property comes from Animal's constructor, while speak is overridden in Dog to provide dog-specific behavior. That is the essence of inheritance: the child gets the parent's capabilities, then adds or changes what it needs.
The extends Keyword
The extends keyword does two things at once: it makes instances of the child class inherit instance methods from the parent's prototype, and it makes the child class itself inherit static methods from the parent class.
The left side of that relationship is static inheritance: Dog itself inherits static members from Animal. The right side is instance inheritance: rex inherits methods from Dog's prototype, which in turn inherits from Animal's prototype.
class Animal {
static kingdom() { return "Animalia"; }
}
class Dog extends Animal {}
console.log(Dog.kingdom()); // "Animalia", static inheritanceBoth chains exist at once, which is why a static method defined on the parent is callable directly on the child class, without needing an instance at all.
The super Keyword
The super keyword has two roles: super() calls the parent constructor, and super.method() calls a method from the parent class.
super() in the Constructor
If the child class defines a constructor, it must call super() before it can use this.
class Vehicle {
constructor(make, model) { this.make = make; this.model = model; }
}
class Car extends Vehicle {
constructor(make, model, doors) {
super(make, model); // must come before using this
this.doors = doors;
}
info() { return `${this.make} ${this.model}, ${this.doors} doors`; }
}
console.log(new Car("Honda", "Civic", 4).info()); // "Honda Civic, 4 doors"If you omit the constructor entirely, JavaScript supplies a default one that passes every argument straight through to super(). You only need to write a constructor of your own when the child needs additional setup or different parameters than the parent takes.
super.method() for Overriding
super.methodName() calls the parent's version of a method from inside the child's overriding version.
class Logger {
log(message) { return `[${new Date().toISOString()}] ${message}`; }
}
class FileLogger extends Logger {
constructor(filename) { super(); this.filename = filename; }
log(message) {
return `${super.log(message)} -> ${this.filename}`; // call the parent's log
}
}
const logger = new FileLogger("app.log");
console.log(logger.log("Server started"));
// e.g. "[2026-07-15T...] Server started -> app.log"Without super.log(), FileLogger would need to duplicate the parent's timestamp formatting logic. With it, the child extends the parent's behavior instead of replacing it outright.
instanceof: Checking the Inheritance Chain
The instanceof operator checks whether an object exists anywhere in a constructor's prototype chain, not just at the exact level it was created at.
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
const rex = new Dog();
console.log(rex instanceof Dog); // true
console.log(rex instanceof Animal); // true, Dog extends Animal
console.log(rex instanceof Cat); // falserex instanceof Animal is true even though rex was created with new Dog(), because instanceof walks the whole chain and returns true as soon as it finds the constructor's prototype anywhere along it.
Overriding Methods
A child class can override any method from the parent by redefining it with the same name. The child's version wins because it sits closer to the instance in the prototype chain.
class Shape {
area() { return 0; }
describe() { return `This shape has an area of ${this.area()}`; }
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
area() { return Math.PI * this.radius ** 2; }
}Square follows the same pattern as Circle, supplying its own area formula while relying on Shape for describe, since only area actually differs between shapes.
class Square extends Shape {
constructor(side) {
super();
this.side = side;
}
area() { return this.side ** 2; }
}
const shapes = [new Circle(5), new Square(4)];
for (const shape of shapes) console.log(shape.describe());
// "This shape has an area of 78.53981633974483"
// "This shape has an area of 16"Notice that describe is only defined on Shape and never overridden. It still calls this.area(), which dispatches to whichever overriding method belongs to the actual instance. That is dynamic dispatch working through the prototype chain.
Extending Built-in Classes
You can extend built-in classes like Error to create custom error types with their own fields.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
try {
throw new ValidationError("Email is required", "email");
} catch (error) {
console.log(error.name); // "ValidationError"
console.log(error.field); // "email"
}Extending Error correctly requires calling super(message) and setting this.name yourself. Other built-ins like Array and Map can also be extended, but watch for edge cases with transpilation and methods that rely on internal engine slots.
Inheritance vs Composition
Inheritance creates a strong is-a relationship: a Dog is an Animal. That is powerful, but it is also rigid, since JavaScript does not support multiple inheritance. If you later want a Robot to speak, you cannot extend both Animal and Machine at once.
Composition is the alternative: build behavior out of smaller, reusable pieces instead of a class hierarchy.
const canSpeak = {
speak() { return `${this.name} says hello.`; }
};
class Bird {
constructor(name) {
this.name = name;
}
}
Object.assign(Bird.prototype, canSpeak);
const tweety = new Bird("Tweety");
console.log(tweety.speak()); // "Tweety says hello."Object.assign copies the speak method onto Bird's prototype, so any object built from smaller pieces like canSpeak can mix in behavior without joining a class hierarchy at all. You could add a second small object, such as canFly, and assign it the same way to give only the birds that need it the ability to fly, without touching Bird's inheritance chain.
| Use inheritance when | Use composition when |
|---|---|
| There is a clear is-a relationship | Behavior needs to be shared across unrelated types |
| The child is a specialized version of the parent | You want to mix and match capabilities |
| The hierarchy is shallow and stable | The types might change independently |
For more on these patterns, see polymorphism in JavaScript and the prototype chain.
Rune AI
Key Insights
- Use extends to create a class that inherits from a parent class.
- Call super() in the child constructor before accessing this.
- Override methods by redefining them in the child class; use super.method() to call the parent version.
- extends sets up both instance inheritance (prototype chain) and static inheritance.
- instanceof checks the entire inheritance chain, so a child instance is also an instance of the parent.
Frequently Asked Questions
Can a class extend multiple classes in JavaScript?
What is the difference between extends with classes and Object.create with plain objects?
Can I extend built-in classes like Array or Error?
Conclusion
Class inheritance in JavaScript uses extends to create a subclass and super to call the parent's constructor and methods. Under the hood, extends wires up the prototype chain so instances can walk up to the parent's methods. Use inheritance when there is a genuine is-a relationship. For sharing behavior between unrelated classes, prefer composition. Check instances with instanceof, override methods by redefining them in the child, and always call super() before using this in a derived constructor.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.