Inheritance and Prototypes in JavaScript
Inheritance and Prototypes in JavaScript
~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Let's extend our account system with a SPECIALIZED account type – a savings account with interest – and along the way, learn what class/extends ACTUALLY does under the hood: JavaScript's prototype system.
extends and super
import { Account } from './account.js';
export class SavingsAccount extends Account {
constructor(name, startingBalance, interestRate) {
super(name, startingBalance); // calls Account's constructor - REQUIRED before 'this'!
this.interestRate = interestRate;
}
creditYearlyInterest() {
const interest = this.balance * (this.interestRate / 100);
this.record('Yearly interest', interest, 'Interest'); // 'record' INHERITED from Account
return interest;
}
formatBalance() {
return `${super.formatBalance()} (${this.interestRate}% interest)`; // OVERRIDE + call base behavior
}
}import { SavingsAccount } from './savingsAccount.js';
const savings = new SavingsAccount('Money Market', 5000, 2.5);
savings.creditYearlyInterest();
console.log(savings.formatBalance()); // 'Money Market: 5125.00 EUR (2.5% interest)'
console.log(savings instanceof Account); // true - SavingsAccount IS-A Accountsuper(...) in the constructor calls the BASE class's constructor – required BEFORE this may be used in a derived constructor. super.formatBalance() inside an overridden method calls the base class's ORIGINAL implementation, instead of replacing it entirely.
What class REALLY is: the prototype system
JavaScript has NO class-based inheritance in the sense of Java or C# – class is "syntactic sugar" over an older, MORE DYNAMIC mechanism: the prototype chain. Every object has an internal link to another object, its "prototype" – if a property is missing on the object itself, JavaScript automatically keeps looking along this chain:
function OldAccount(name, balance) {
this.name = name;
this.balance = balance;
}
// Define methods on the PROTOTYPE instead of on every instance:
OldAccount.prototype.formatBalance = function () {
return `${this.name}: ${this.balance.toFixed(2)} EUR`;
};
const account = new OldAccount('Checking', 1500);
console.log(account.formatBalance()); // 'Checking: 1500.00 EUR'
// 'account' has NO own 'formatBalance' property -
// JavaScript finds it via the prototype chain on OldAccount.prototypeThis is EXACTLY the same pattern class automatically creates for us – methods defined in a class body land internally on the class's prototype, not on every single instance. This also explains why methods are shared MEMORY-EFFICIENTLY: only ONE copy of each method exists, no matter how many Account instances are created.
The prototype chain with extends
console.log(Object.getPrototypeOf(savings) === SavingsAccount.prototype); // true
console.log(Object.getPrototypeOf(SavingsAccount.prototype) === Account.prototype); // true
console.log(savings instanceof SavingsAccount); // true
console.log(savings instanceof Account); // true - found via the chain
console.log(savings instanceof Object); // true - EVERY object ends up here eventuallyTipp: For everyday use, knowing class/extends/super is enough – but the prototype knowledge from this section explains WHY JavaScript behaves this way, and helps in understanding error messages like "X is not a function" (the method exists neither on the object nor anywhere in its prototype chain).
Polymorphism: same method, different behavior
const accounts = [
new Account('Checking', 1500),
new SavingsAccount('Money Market', 5000, 2.5),
];
for (const account of accounts) {
// EACH account calls ITS OWN version of formatBalance() - polymorphism
console.log(account.formatBalance());
}
// 'Checking: 1500.00 EUR'
// 'Money Market: 5000.00 EUR (2.5% interest)'