JavaScript · Classes & OOP

JavaScript Class Inheritance — extends, super, and Method Overriding Explained

6 min read Updated 2026-06-18

Practice Inheritance with extends & super interview questions

Inheritance with extends

The extends keyword lets one class build on another, inheriting its methods and adding or overriding behavior. Under the hood it wires up the same prototype chain you'd construct by hand with constructor functions — but cleanly and correctly, removing the manual steps that used to cause bugs. If you've read how prototypal inheritance works, class ... extends is simply the ergonomic, standardized version of it.

class Animal {
  constructor(name) { this.name = name }
  speak() { return `${this.name} makes a sound` }
}

class Dog extends Animal {
  speak() { return `${this.name} barks` }   // override
}

const d = new Dog('Rex')
d.speak()            // 'Rex barks'
d instanceof Animal  // true

Dog inherits Animal's constructor behavior and any methods it doesn't override. The chain is d -> Dog.prototype -> Animal.prototype -> Object.prototype -> null.

What extends sets up

extends does two pieces of prototype wiring that you'd otherwise write manually:

  • It links the instance chain: Dog.prototype's prototype becomes Animal.prototype, so instances inherit instance methods.
  • It links the static chain: Dog's prototype becomes Animal, so static methods are inherited too (more on that below).
Object.getPrototypeOf(Dog.prototype) === Animal.prototype   // true (instance methods)
Object.getPrototypeOf(Dog) === Animal                       // true (static methods)

This dual linkage is something the old constructor pattern handled awkwardly; extends does both correctly every time.

super in the constructor

When a subclass defines its own constructor, it must call super(...) before using this. super() invokes the parent constructor, which is what actually creates and initializes this for a derived class.

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

class Dog extends Animal {
  constructor(name, breed) {
    super(name)          // must come first
    this.breed = breed   // now `this` is available
  }
}

new Dog('Rex', 'Lab')   // { name: 'Rex', breed: 'Lab' }

The ordering rule is strict and enforced:

class Bad extends Animal {
  constructor(name) {
    this.x = 1    // ReferenceError — `this` before super()
    super(name)
  }
}

This is because, in a derived class, this literally doesn't exist until the parent constructor produces it. Always call super first.

The implicit constructor

If a subclass doesn't define a constructor, JavaScript inserts a default one that forwards all arguments to the parent:

class Cat extends Animal {}   // implicitly: constructor(...args){ super(...args) }
new Cat('Whiskers').name      // 'Whiskers' forwarded automatically

So you only need to write a constructor in the subclass when you want to add parameters or extra initialization. If you don't, inheritance "just works."

super in methods

Inside a method, super.methodName() calls the parent's version of that method — the clean replacement for the old Parent.prototype.method.call(this) idiom. This lets you extend rather than fully replace inherited behavior.

class Animal {
  describe() { return `I am ${this.name}` }
}

class Dog extends Animal {
  describe() {
    return `${super.describe()} and I bark`   // build on the parent
  }
}

new Dog('Rex').describe()   // 'I am Rex and I bark'

super in a method resolves to the parent prototype but keeps this bound to the current instance — so super.describe() runs Animal's method with the dog's data.

Overriding methods

Overriding is just defining a method with the same name in the subclass; it shadows the parent's version for instances of the subclass. You can fully replace or partially extend with super.

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

class Square extends Shape {
  constructor(side) { super(); this.side = side }
  area() { return this.side ** 2 }   // override — toString() now uses THIS area()
}

new Square(4).toString()   // 'Shape with area 16' polymorphism

Notice Shape.toString calls this.area(), which resolves to Square's override — that's polymorphism: the inherited method automatically uses the subclass's behavior.

Inheriting static methods

Static methods are inherited too, thanks to the static side of the chain that extends sets up. A subclass can call its parent's static methods, and super works in static methods as well.

class Model {
  static create(data) { return new this(data) }   // `this` is the calling class
}
class User extends Model {}

User.create({ name: 'Ada' }) instanceof User   // true `this` is User

Because this in a static method is the class it was called on, Model.create builds a User when invoked as User.create — a powerful pattern for factory methods.

Extending built-ins

You can extend built-in classes like Array, Error, or Map.

class Stack extends Array {
  peek() { return this[this.length - 1] }
}
const s = new Stack()
s.push(1, 2, 3)
s.peek()   // 3

Custom Error subclasses are especially common for typed error handling. One caveat: extending some built-ins has historical quirks when transpiled to ES5, but in modern environments it works as expected.

Common pitfalls

A few mistakes recur with class inheritance:

// using this before super()
class A extends Base { constructor() { this.x = 1; super() } }

// forgetting super() entirely in a derived constructor
class B extends Base { constructor() { /* no super */ } }  // ReferenceError on use

// assuming super refers to the parent INSTANCE — it refers to the parent PROTOTYPE

The mental model that prevents all three: in a derived class, this is manufactured by super(), so it can't precede it; and super.x() means "the parent's method, run with my this," not "a separate parent object."

Key takeaways

  • extends links both the instance prototype chain and the static chain — full inheritance, correctly wired.
  • A derived constructor must call super(...) before touching this; omit the constructor entirely to auto-forward arguments.
  • super.method() calls the parent's method with the current this, enabling extend-not- replace overrides.
  • Overriding plus methods that call this.other() give you polymorphism for free.
  • Static methods are inherited; this in a static method is the calling class, enabling factory patterns.
  • You can extend built-ins like Array and Error; just call super() correctly.

extends and super are the polished front-end of prototypal inheritance. Knowing what they compile to keeps every edge case predictable.

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.