Skip to content
JavaScript js classes 3 min read

Inheritance with extends

Inheritance lets one class build on another, reusing its data and behavior while adding or refining its own. In JavaScript you express this with the extends keyword, which creates a subclass (child) that derives from a superclass (parent). This is the cleanest way to model “is-a” relationships and avoid duplicating logic across related types.

Extending a class

A subclass declared with extends automatically inherits all the methods of its parent through the prototype chain. You only write the parts that differ.

class Animal {
  constructor(name) {
    this.name = name;
  }

  describe() {
    return `${this.name} is an animal.`;
  }

  speak() {
    return `${this.name} makes a sound.`;
  }
}

class Dog extends Animal {
  fetch() {
    return `${this.name} fetches the ball.`;
  }
}

const rex = new Dog("Rex");
console.log(rex.describe());
console.log(rex.fetch());

Output:

Rex is an animal.
Rex fetches the ball.

Dog never defines describe() or a constructor, yet both work — they are looked up on Animal.prototype. Dog adds only the new fetch() method.

Overriding methods

A subclass can redefine an inherited method by declaring a method with the same name. The subclass version shadows the parent’s, so calls on an instance resolve to the most specific implementation.

class Cat extends Animal {
  speak() {
    return `${this.name} says meow.`;
  }
}

const felix = new Cat("Felix");
console.log(felix.speak());
console.log(felix.describe());

Output:

Felix says meow.
Felix is an animal.

Cat.speak() overrides Animal.speak(), while describe() is still inherited unchanged. This is polymorphism: code that calls animal.speak() gets the right behavior for whatever subclass it actually holds.

Calling parent methods with super

Overriding often means extending the parent’s behavior rather than replacing it entirely. Inside a method, super.methodName() invokes the parent’s version, letting you wrap it.

class Bird extends Animal {
  speak() {
    return `${super.speak()} It chirps.`;
  }
}

const robin = new Bird("Robin");
console.log(robin.speak());

Output:

Robin makes a sound. It chirps.

When a subclass defines its own constructor, it must call super(...) before touching this. super() runs the parent constructor so inherited fields are initialized first.

class Animal {
  constructor(name) {
    this.name = name;
  }
  describe() {
    return `${this.name} is an animal.`;
  }
}

class Puppy extends Animal {
  constructor(name, age) {
    super(name);      // initialize the parent first
    this.age = age;   // then add subclass state
  }
  describe() {
    return `${super.describe()} It is ${this.age} months old.`;
  }
}

const buddy = new Puppy("Buddy", 4);
console.log(buddy.describe());

Output:

Buddy is an animal. It is 4 months old.

Referencing this before calling super() in a subclass constructor throws a ReferenceError. Always call super() first.

A small hierarchy

Hierarchies can be more than two levels deep. Each link in the chain extends the one above it, and method lookups walk up the chain until a match is found.

class Shape {
  area() {
    return 0;
  }
  toString() {
    return `${this.constructor.name} with area ${this.area()}`;
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }
  area() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  constructor(side) {
    super(side, side);
  }
}

const shapes = [new Rectangle(3, 4), new Square(5)];
for (const shape of shapes) {
  console.log(shape.toString());
}

Output:

Rectangle with area 12
Square with area 25

Square extends Rectangle, which extends Shape. The inherited toString() calls area(), which dynamically resolves to each subclass’s implementation.

Inheritance terms at a glance

TermMeaning
extendsDeclares that a class inherits from another
Superclass / parentThe class being extended
Subclass / childThe class doing the extending
OverrideRedefining an inherited method in a subclass
super(...)Calls the parent constructor (in a subclass constructor)
super.method()Calls the parent’s version of a method

Best practices

  • Use inheritance only for true “is-a” relationships; prefer composition for “has-a” or behavior sharing across unrelated types.
  • Keep hierarchies shallow — deep chains are hard to reason about and refactor.
  • Always call super() first in a subclass constructor before referencing this.
  • Override to extend with super.method() when you want parent behavior plus your own.
  • Design parent methods so subclasses can safely override them (small, focused, well-named).
  • Reach for mixins when you need to share behavior across classes that don’t fit a single hierarchy.
Last updated June 1, 2026
Was this helpful?