~/hackweb.dev
JavaScript Inheritance & extends
Quiz
...

JavaScript Inheritance & extends

beginner · updated Tue Sep 08 2026Contribute

Master class inheritance, super keyword, and method overriding.

JavaScript Inheritance & extends

Create specialized classes from parent classes using extends.

Basic Inheritance

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

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

let dog = new Dog("Rex");
console.log(dog.speak());  // "Rex barks"

Dog inherits name from Animal and overrides speak().

The super Keyword

class Dog extends Animal {
  constructor(name, breed) {
    super(name);  // Call parent constructor
    this.breed = breed;
  }

  describe() {
    return `${super.describe()} (${this.breed})`;
  }
}

Remember: Call super() before using this in a child constructor.

Method Overriding & Polymorphism

class Shape {
  constructor(name) { this.name = name; }
  getArea() { throw new Error("getArea() must be implemented"); }
  toString() { return `${this.name} with area ${this.getArea()}`; }
}

class Circle extends Shape {
  constructor(r) { super("Circle"); this.radius = r; }
  getArea() { return Math.PI * this.radius ** 2; }
}

class Rectangle extends Shape {
  constructor(w, h) { super("Rectangle"); this.w = w; this.h = h; }
  getArea() { return this.w * this.h; }
}

[new Circle(5), new Rectangle(4, 6)].forEach(s => console.log(s.toString()));
// "Circle with area 78.539..."
// "Rectangle with area 24"

Same toString(), different results — this is polymorphism.

Complete Example: Employee System

class Employee {
  constructor(name, salary) { this.name = name; this.salary = salary; }
  getBonus() { return this.salary * 0.1; }
  describe() { return `${this.name}: $${this.salary} + $${this.getBonus()} bonus`; }
}

class Manager extends Employee {
  constructor(name, salary, dept) {
    super(name, salary);
    this.department = dept;
    this.reports = [];
  }
  getBonus() { return this.salary * 0.2; }
  addReport(emp) { this.reports.push(emp); return this; }
  describe() { return `${super.describe()} (Manager of ${this.department})`; }
}

class Developer extends Employee {
  constructor(name, salary, langs) {
    super(name, salary);
    this.languages = langs;
  }
  getBonus() { return this.salary * 0.15; }
  describe() { return `${super.describe()} (Dev: ${this.languages.join(", ")})`; }
}

let dev = new Developer("Alice", 80000, ["JS", "Python"]);
let mgr = new Manager("John", 120000, "Engineering");
console.log(dev.describe());  // "Alice: $80000 + $12000 bonus (Dev: JS, Python)"
console.log(mgr.describe());  // "John: $120000 + $24000 bonus (Manager of Engineering)"

Each subclass overrides getBonus() with a different rate — same interface, different behavior.

Best Practices

  • Use extends for “is-a” relationships — Dog is an Animal
  • Call super() first — Before using this in child constructor
  • Override methods carefully — Maintain the parent’s interface
  • Keep hierarchies shallow — Deep inheritance is hard to maintain
  • Prefer composition — “Has-a” often beats “is-a”

Common Mistakes

  • Forgetting super() — Required in child constructor
  • Overriding with different parameters — Breaks the interface contract
  • Deep inheritance chains — Hard to maintain and debug
  • Not returning this — Breaks method chaining