JS::OOP_Mastery
Overview / Session 05
Session 05 of 10  ·  2 hrs + 15 min review

Inheritance & super

⏱ 2-hour session 📋 15-min review 🟡 Builds on Sessions 02–03 🗂 4 topics

1 — Building on an Existing Class

Say you have a Vehicle class with shared behaviour, and now you need a Car and a Motorcycle that both have that behaviour plus something extra. Copying Vehicle's code into both new classes works, but now a bug fix has to happen in three places instead of one.

extends lets one class inherit everything from another: its properties, its methods, its whole shape. The class doing the inheriting is the subclass (or child class); the one being inherited from is the superclass (or parent class).

JavaScript — extends basics
class Vehicle {
    constructor(brand) {
        this.brand = brand;
    }
    describe() {
        return `A ${this.brand} vehicle`;
    }
}

class Car extends Vehicle {
    // nothing added yet — Car already has brand AND describe() for free
}

const car = new Car('Toyota');
console.log(car.describe()); // "A Toyota vehicle" — inherited, not rewritten
console.log(car instanceof Vehicle); // true
console.log(car instanceof Car);     // true — also itself

Under the hood, this is exactly the prototype chain from Session 03 — just one link longer. Car.prototype's own prototype is now Vehicle.prototype, so a lookup for describe() on a Car instance walks up the chain exactly the way it did before.

2 — super() in the Constructor

The moment your subclass needs its own constructor — to accept extra parameters, say — you must call super() before you can use this. super() runs the parent class's constructor, which is what actually sets up the inherited properties.

JavaScript — super() in the constructor
class Vehicle {
    constructor(brand) {
        this.brand = brand;
    }
}

class Car extends Vehicle {
    constructor(brand, doors) {
        super(brand);     // MUST come first — runs Vehicle's constructor
        this.doors = doors; // now safe to use `this` for Car's own property
    }
}

const car = new Car('Honda', 4);
console.log(car.brand); // "Honda" — set by Vehicle's constructor via super()
console.log(car.doors); // 4 — set by Car's own constructor

// Try this instead, without calling super() first:
class Broken extends Vehicle {
    constructor(brand) {
        this.brand = brand; // ReferenceError: Must call super constructor first
    }
}

Why the strict ordering? In a subclass constructor, this doesn't exist yet until the parent constructor runs and initializes it. JavaScript enforces this with a hard error rather than letting you use an uninitialized object — one of the few places JS is stricter than you might expect.

One shortcut worth knowing: if your subclass doesn't need its own constructor at all (like Car in Topic 1), you can simply omit it — JavaScript generates an implicit one that just calls super(...args) for you.

3 — Overriding Methods & super.method()

A subclass can redefine a method it inherited — this is called overriding. Sometimes you want to replace the parent's behaviour entirely; other times you want to extend it — run the parent's version, then add something on top. That's what super.method() is for.

JavaScript — overriding and calling the parent version
class Vehicle {
    constructor(brand) { this.brand = brand; }

    describe() {
        return `A ${this.brand} vehicle`;
    }
}

class Car extends Vehicle {
    constructor(brand, doors) {
        super(brand);
        this.doors = doors;
    }

    describe() {                       // OVERRIDE — completely replaces Vehicle's version
        return `${super.describe()}, ${this.doors}-door`;
        // ^ calls Vehicle's describe() first, then extends the result
    }
}

const car = new Car('Mazda', 2);
console.log(car.describe()); // "A Mazda vehicle, 2-door"

Without super.describe() inside the override, you'd have to retype the parent's whole sentence to extend it. With it, the override builds directly on the parent's existing logic — change Vehicle.describe() once, and every subclass override that calls super.describe() picks up the change automatically.

4 — When Not to Use Inheritance

Inheritance models an "is-a" relationship: a Car is a Vehicle. That's a clean fit. Problems start when people reach for extends for things that are really a "has-a" relationship instead.

JavaScript — a tempting but wrong use of inheritance
// "A Car HAS an Engine" — not "a Car IS an Engine"
class Engine {
    start() { return 'Engine running'; }
}

// WRONG — this says a Car IS an Engine, which doesn't make sense
class Car extends Engine {
    drive() { return 'Driving...'; }
}
// Now Car is permanently locked into being a kind of Engine.
// What happens when you add an ElectricCar with no traditional engine at all?

// BETTER — composition: a Car HAS an engine as a property
class Car {
    constructor() {
        this.engine = new Engine(); // composed, not inherited
    }
    drive() {
        return `${this.engine.start()}, driving...`;
    }
}

The fixed version reads naturally as English: a car has an engine. It's also far more flexible — swap in a different engine object, or none at all for an ElectricCar, without touching the inheritance tree at all. A useful rule of thumb: if you can't honestly say "X is a Y" out loud about your classes, reach for composition (an object holding another object as a property) instead of extends. Session 09 covers composition patterns in depth — for now, just recognize the warning sign.

Long inheritance chains cause a related problem: a change to a class three levels up can silently break a class three levels down that nobody remembers is connected. Favor shallow, well-justified "is-a" hierarchies over deep ones.

15-Minute Review — Session 05

Stop the session timer and switch to the review timer in the sidebar. Answer these questions from memory — then check.

Q1 — What does class Car extends Vehicle give the Car class?

Q2 — In a subclass constructor that defines its own logic, what happens if you use this before calling super()?

Q3 — Inside an overridden method, what does calling super.describe() do?

Q4 — A Car needs an Engine. Why is "a Car has an Engine" (composition) usually better than "a Car extends Engine" (inheritance)?

✓ Key concepts checklist

Next: Polymorphism & Duck Typing →