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.

7 min read

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.

javascriptjavascript
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 constructor

Dog 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.

Prototype chain created by extends

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.

javascriptjavascript
class Animal {
  static kingdom() { return "Animalia"; }
}
class Dog extends Animal {}
 
console.log(Dog.kingdom()); // "Animalia", static inheritance

Both 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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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);    // false

rex 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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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 whenUse composition when
There is a clear is-a relationshipBehavior needs to be shared across unrelated types
The child is a specialized version of the parentYou want to mix and match capabilities
The hierarchy is shallow and stableThe types might change independently

For more on these patterns, see polymorphism in JavaScript and the prototype chain.

Rune AI

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.
RunePowered by Rune AI

Frequently Asked Questions

Can a class extend multiple classes in JavaScript?

No. JavaScript only supports single inheritance. A class can extend exactly one parent class. To combine behavior from multiple sources, use mixins (functions that take a class and return an extended version) or composition (storing instances of other classes as properties).

What is the difference between extends with classes and Object.create with plain objects?

extends sets up both the instance prototype chain (instances inherit from parent's prototype) and the class prototype chain (the child class inherits static members from the parent). Object.create only sets up the instance chain. extends also enforces calling super() in the child constructor.

Can I extend built-in classes like Array or Error?

Yes. You can extend Array, Error, Map, and other built-ins. However, extending built-ins has known edge cases with transpilation and older JavaScript engines. For Error, make sure to call super(message) and set this.name in the constructor.

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.