AlgoInsight

Free lesson · JavaScript Basic to Advance

The Prototype Chain

Easy5 min read

How JavaScript Objects Inherit

Every JavaScript object has a hidden internal property that points to another object — its prototype. When you access a property on an object, if the object itself doesn't have that property, JavaScript looks at the prototype. If the prototype doesn't have it either, JavaScript looks at the prototype's prototype. This chain continues until either the property is found, or the chain ends at null.

const animal = {
  eats: true,
  walk() {
    console.log("Walking");
  }
};

const dog = Object.create(animal);   // dog's prototype is animal
dog.barks = true;

console.log(dog.barks);   // true — own property
console.log(dog.eats);    // true — inherited from animal
dog.walk();               // "Walking" — inherited from animal

dog has one own property: barks. When we access dog.eats, JavaScript looks at dog first (not found), then at dog's prototype (which is animal), finds eats there, and returns it. Same with walk() — it's a method on animal, but dog can call it because of the prototype chain.

This is JavaScript's inheritance mechanism. It's called prototypal inheritance, and it's fundamentally different from the class-based inheritance you might know from other languages. Classes in JavaScript (Module 8) are syntactic sugar over this prototype mechanism. The mechanism itself is what we're covering here.

This is the most-misunderstood part of JavaScript. Take it slowly.

Every Object Has a Prototype

Almost every object in JavaScript has a prototype. The only exceptions are objects created with Object.create(null), which deliberately have no prototype.

const obj = {};
console.log(Object.getPrototypeOf(obj));   // {} (this is Object.prototype)
console.log(Object.getPrototypeOf(Object.getPrototypeOf(obj)));   // null — the end of the chain

const arr = [];
console.log(Object.getPrototypeOf(arr));   // [] (this is Array.prototype)

const fn = function() {};
console.log(Object.getPrototypeOf(fn));    // function (Function.prototype)

Object.getPrototypeOf(obj) returns the prototype of obj. For a plain object created with {}, the prototype is Object.prototype — the root prototype that every plain object inherits from. Object.prototype itself has a prototype of null, which is where the chain ends.

For arrays, the prototype is Array.prototype — that's why arrays have methods like map, filter, reduce. Those methods are defined on Array.prototype and every array inherits them.

For functions, the prototype is Function.prototype — that's why functions have methods like bind, call, apply.

graph TD
    A[your object] --> B[Object.prototype]
    B --> C[null]

    D[your array] --> E[Array.prototype]
    E --> B

    F[your function] --> G[Function.prototype]
    G --> B

    style A fill:#10b981,color:#fff
    style D fill:#10b981,color:#fff
    style F fill:#10b981,color:#fff
    style C fill:#dc2626,color:#fff
This diagram shows the prototype chain for common JavaScript values. A plain object you create has Object.prototype as its prototype, and Object.prototype itself has null as its prototype, ending the chain. An array has Array.prototype as its prototype, which in turn has Object.prototype as its prototype. Same for functions: a function has Function.prototype as its prototype, which has Object.prototype as its prototype. All chains eventually terminate at null.

This is why every JavaScript object — even ones you didn't define methods for — has methods like toString, hasOwnProperty, valueOf. Those methods are on Object.prototype, and every object inherits from it.

Interactive animation: Where push was this whole time - the array misses, and the lookup finds it on Array.prototype.

How Property Lookup Walks the Chain

When you read a property, JavaScript follows a specific lookup procedure:

  1. Look at the object itself. If the property is found, return its value.
  2. If not, look at the object's prototype. If the property is found there, return its value.
  3. If not, look at the prototype's prototype. Continue walking up.
  4. If you reach null without finding the property, return undefined.
const grandparent = { surname: "Joshi" };
const parent = Object.create(grandparent);
parent.lastName = "Joshi";   // intentional duplicate-ish for the demo
const child = Object.create(parent);
child.firstName = "Nikhil";

console.log(child.firstName);   // "Nikhil" — own property
console.log(child.lastName);    // "Joshi" — found on parent
console.log(child.surname);     // "Joshi" — found on grandparent
console.log(child.missing);     // undefined — not found anywhere

console.log(Object.getPrototypeOf(child) === parent);            // true
console.log(Object.getPrototypeOf(parent) === grandparent);      // true
console.log(Object.getPrototypeOf(grandparent) === Object.prototype);  // true

The chain is: child → parent → grandparent → Object.prototype → null. Looking up surname on child walks through child, parent, and finally finds it on grandparent.

This lookup is what creates inheritance. Without inheriting properties through a chain, every object would need its own copy of every property and method. With the chain, common methods can live on a single prototype and be shared by many objects.

Interactive animation: Miss, follow, repeat - the lookup checks the object, then walks up its prototypes until it finds a match.

Setting Properties Doesn't Walk the Chain

Property reads walk the prototype chain. Property writes don't.

const animal = { sound: "generic noise" };
const dog = Object.create(animal);

console.log(dog.sound);   // "generic noise" — inherited

dog.sound = "Bark!";       // creates own property on dog (does NOT modify animal)

console.log(dog.sound);    // "Bark!" — own property
console.log(animal.sound); // "generic noise" — unchanged

const cat = Object.create(animal);
console.log(cat.sound);    // "generic noise" — animal.sound is still the original

When we wrote dog.sound = "Bark!", we didn't modify animal.sound. We created a new own property on dog that shadows the inherited one. Going forward, reading dog.sound returns the own property; reading cat.sound (which doesn't have its own) still gets the inherited value.

This is important: you can read shared properties through inheritance, but writing creates an own property on the writing object. Properties on the prototype stay shared until something explicitly shadows them.

Interactive animation: Reads walk, writes stay home - an own property shadows the inherited one for this object only.

Methods on the Prototype

The most common reason to use prototypes is to share methods between many objects. Each instance has its own data, but they all share method implementations through a common prototype.

const userProto = {
  greet() {
    return `Hi, I'm ${this.name}`;
  },

  describe() {
    return `${this.name} is ${this.age} years old`;
  }
};

const olena = Object.create(userProto);
olena.name = "Olena";
olena.age = 35;

const nikhil = Object.create(userProto);
nikhil.name = "Nikhil";
nikhil.age = 32;

console.log(olena.greet());      // "Hi, I'm Olena"
console.log(nikhil.greet());     // "Hi, I'm Nikhil"
console.log(olena.describe());   // "Olena is 35 years old"

// Both olena and nikhil share the SAME greet function via prototype
console.log(olena.greet === nikhil.greet);   // true

olena and nikhil each have their own name and age. They share greet and describe through the prototype. The methods use this to refer to the calling object — so olena.greet() produces a different result from nikhil.greet(), even though they're calling the same function.

This is the entire foundation of how JavaScript "classes" work. When we cover classes in Module 8, you'll see they're just a clean syntax wrapper over this pattern — class User { greet() {...} } is essentially defining methods on a prototype and providing a clean way to create new instances.

Interactive animation: The design reason - methods stored once on a prototype and shared, instead of rebuilt for every instance.

Inspecting the Chain

A few tools for understanding what's in an object's prototype chain:

const obj = { x: 1 };

// Get the immediate prototype
console.log(Object.getPrototypeOf(obj) === Object.prototype);   // true

// Check whether a specific prototype is in the chain
console.log(Object.prototype.isPrototypeOf(obj));   // true

// instanceof checks whether something is "an instance of" a constructor's prototype
const arr = [];
console.log(arr instanceof Array);    // true — Array.prototype is in arr's chain
console.log(arr instanceof Object);   // true — Object.prototype is also in the chain

instanceof checks if a given constructor's prototype object appears anywhere in the chain. This is how JavaScript answers "is this thing an array?" or "is this thing an error?" — by walking the chain.

Common Pitfalls

Warning: The prototype chain traps.

Modifying built-in prototypes. Adding properties to Array.prototype or Object.prototype affects every object in your program — including ones in libraries you're using. This was a notorious source of bugs in early jQuery-era JavaScript. Don't do it.

Confusing prototype with __proto__. Most older code uses obj.__proto__ to access the prototype. Modern code prefers Object.getPrototypeOf(obj) because __proto__ is technically deprecated (though still widely supported). Both refer to the same thing.

Forgetting that for...in walks the chain. Topic 7.4 covered this — for...in iterates inherited enumerable properties, not just own properties. Use Object.keys for own-properties-only iteration.

Assuming property writes affect the prototype. They don't. Writing creates an own property on the writing object. This is the source of bugs where developers think they're modifying shared state but are actually shadowing it.

Why This Matters in Interviews

Interview: Prototype questions are common at senior levels. "Explain prototypal inheritance" expects an answer covering the lookup mechanism, the chain ending at null, and how methods get shared. "What's the difference between classical and prototypal inheritance?" expects an answer about classes being templates vs prototypes being live objects.

Topic 7.8 — Interview Lens: Objects and Prototypes — covers the deeper interview treatment, including the implementation challenges interviewers use to test prototype understanding.

Try It

Exercise: Build a small inheritance chain manually using Object.create:

  1. Create a vehicle object with a wheels property (4) and a start() method that logs "Starting..."
  2. Create a car object whose prototype is vehicle. Give it a model property.
  3. Create a sportsCar object whose prototype is car. Give it a topSpeed property and a boost() method.
  4. Create an instance: const myCar = Object.create(sportsCar); and assign it some properties.

Then verify:

  • myCar can access properties from all four levels of the chain
  • Modifying a property on myCar doesn't affect the prototypes
  • Object.getPrototypeOf(myCar) === sportsCar returns true
  • myCar instanceof Object returns true (since Object.prototype is eventually in the chain)

Trace the chain on paper. The exercise of building it manually is what makes prototypal inheritance click. Once you've done this, classes in Module 8 will feel like a natural shortcut for the same mechanism.

Back to JavaScript Basic to Advance