Skip to content

Inheritance with extends

Sometimes you need several classes that share a common foundation but differ in specific ways. Inheritance lets one class (extends) build on another, gaining all of its properties and methods while adding or overriding what it needs.

class RecurringExpense extends Expense {
constructor({ recurrence, ...rest }) {
super(rest);
this.recurrence = recurrence; // 'monthly', 'weekly', etc.
}
}

RecurringExpense is a subclass (or child class) of Expense. Every instance of RecurringExpense automatically has all the properties and methods of Expense, plus the recurrence property.

The first thing a subclass constructor must do is call super(), passing the arguments the parent constructor needs. This runs the parent’s constructor and sets up this — you cannot use this before calling super.

class RecurringExpense extends Expense {
constructor({ recurrence, ...rest }) {
super(rest); // must come first — runs Expense's constructor
this.recurrence = recurrence;
}
nextDueDate() {
const base = new Date(this.date);
if (this.recurrence === 'monthly') {
base.setMonth(base.getMonth() + 1);
} else if (this.recurrence === 'weekly') {
base.setDate(base.getDate() + 7);
}
return base.toISOString().split('T')[0];
}
}
const gym = new RecurringExpense({
description: 'Gym membership',
amount: 45.00,
category: 'Health',
date: '2024-01-10',
recurrence: 'monthly',
});
console.log(gym.format()); // 'Gym membership: $45.00' — inherited from Expense
console.log(gym.nextDueDate()); // '2024-02-10'
console.log(gym instanceof Expense); // true
console.log(gym instanceof RecurringExpense); // true

gym passes both instanceof checks — it is both an Expense and a RecurringExpense.

A subclass can override a parent method by defining a method with the same name:

class RecurringExpense extends Expense {
constructor({ recurrence, ...rest }) {
super(rest);
this.recurrence = recurrence;
}
// Override Expense's format() to include recurrence info
format() {
return `${super.format()} (${this.recurrence})`;
}
}
console.log(gym.format()); // 'Gym membership: $45.00 (monthly)'

super.format() calls the parent class’s version of format. This lets you extend the parent’s behavior rather than replace it entirely.

Inheritance is powerful but easy to overuse. Use it when:

  • A clear “is-a” relationship exists: a RecurringExpense is an Expense
  • The subclass genuinely needs everything the parent has
  • You are specializing behavior, not just grouping unrelated functionality

Avoid inheritance when the relationship is “has-a” or when you just need to share some utility functions. In those cases, composition (having one class hold an instance of another) or a shared utility module is usually cleaner.

For BudgetBuddy, a single Expense class is enough — no inheritance needed. The RecurringExpense here is an illustrative example.

  1. Using your Expense class from the previous lessons, create a RecurringExpense subclass that adds a recurrence property ('daily', 'weekly', 'monthly').
  2. Override format() in RecurringExpense to append the recurrence, e.g. 'Gym membership: $45.00 (monthly)'.
  3. Create one RecurringExpense instance and confirm:
    • It has the description, amount, category, and date properties from Expense
    • It has the recurrence property
    • format() returns the overridden version
    • instanceof Expense is true
    • instanceof RecurringExpense is true
  • extends creates a subclass that inherits all properties and methods from the parent.
  • super() must be called first in a subclass constructor — it runs the parent constructor and sets up this.
  • super.method() calls the parent’s version of a method from inside an override.
  • Subclass instances pass instanceof checks for both the subclass and the parent.
  • Use inheritance for genuine “is-a” relationships; avoid it for utility sharing or “has-a” relationships.