Prototypes — How Inheritance Actually Works
IntermediateJavaScript objects delegate to other objects through a prototype chain. Classes are syntax over this mechanism, not a separate system — which is why understanding the chain explains behaviour classes cannot.
Overview
JavaScript has no classes underneath. When you read a property, the engine looks on the object; if it is not there, it follows a hidden link to another object and looks again, repeating until it finds the property or reaches null. That chain is the whole inheritance model. The class keyword added in 2015 is sugar over it — genuinely useful sugar, and you should use it — but the moment you debug an unexpected inherited property, or wonder why an object has a toString you never wrote, you are looking at the chain.
The Chain
Every object has a hidden [[Prototype]] link. Property lookup walks it. Assignment does not — it always writes to the object itself.
const animal = { breathes: true, describe() { return 'a living thing' } }
const dog = Object.create(animal)
dog.barks = true
dog.barks // true — own property
dog.breathes // true — found on animal via the chain
dog.describe() // 'a living thing'
dog.toString() // inherited from Object.prototype
Object.getPrototypeOf(dog) === animal // true
Object.hasOwn(dog, 'breathes') // false — inherited
// The full chain for a plain array:
// [] -> Array.prototype -> Object.prototype -> nullClasses Are the Same Thing
class produces a constructor function whose .prototype holds the methods. Every instance links to it — so methods are shared, not copied per instance.
class Animal {
constructor(name) { this.name = name }
speak() { return this.name + ' makes a sound' }
}
class Dog extends Animal {
speak() { return super.speak() + ' (a bark)' }
}
const d = new Dog('Bruno')
Object.getPrototypeOf(d) === Dog.prototype // true
Object.getPrototypeOf(Dog.prototype) === Animal.prototype // true
// speak lives once on the prototype, not on each instance:
Object.hasOwn(d, 'speak') // false
Object.hasOwn(d, 'name') // trueModern Class Features
Private fields give real encapsulation — enforced by the engine, not by convention. This is what closures used to be needed for.
class Account {
#balance = 0 // genuinely private
static #count = 0
constructor(owner) {
this.owner = owner
Account.#count++
}
deposit(n) { this.#balance += n; return this }
get balance() { return this.#balance }
static get total() { return Account.#count }
}
const acc = new Account('Asha').deposit(500)
acc.balance // 500
acc.#balance // SyntaxError — not accessible outside the classKey Points to Remember
- 1Property lookup walks the prototype chain; assignment always writes to the object itself
- 2class is syntax over prototypes — extends sets the chain, super walks up it
- 3Methods live once on the prototype and are shared by every instance, not copied
- 4Object.hasOwn() distinguishes own properties from inherited ones — important when iterating
- 5#private fields are enforced by the engine, which is what closures were being used for before
Interview Questions
Sign in to ask AriaExplain the prototype chain. What happens when you read a property that does not exist on an object?
Are JavaScript classes real classes? What are they built on?
Where do class methods live — on the instance or the prototype? Why does it matter for memory?
Ask Aria about Prototypes — How Inheritance Actually Works
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.