Home/Learn/JavaScript & TypeScript/Prototypes — How Inheritance Actually Works

Prototypes — How Inheritance Actually Works

Intermediate
Language Core

JavaScript 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.

Lookup walks the chain; assignment does not
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 -> null

Classes 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, and what it compiles down to
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')           // true

Modern Class Features

Private fields give real encapsulation — enforced by the engine, not by convention. This is what closures used to be needed for.

Private fields, static members, getters
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 class

Key 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 Aria
1

Explain the prototype chain. What happens when you read a property that does not exist on an object?

Medium
2

Are JavaScript classes real classes? What are they built on?

Medium
3

Where do class methods live — on the instance or the prototype? Why does it matter for memory?

Hard

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.

Loading discussion…