JavaScript Prototype Chain Explained: How Inheritance Works
The prototype chain is the mechanism JavaScript uses to look up properties and methods on objects beyond the object itself. Understanding it helps you read object-oriented JavaScript, debug missing properties, and use built-in objects correctly.
Quick answer: When you access a property on an object, JavaScript first checks the object itself. If it does not find the property there, it follows the prototype chain to the object’s prototype, then the prototype’s prototype, and so on until it reaches null.
Difficulty: Beginner
You'll understand this better if you know: basic JavaScript objects, how dot notation works, and the difference between values stored on an object versus values inherited from somewhere else.
1. What Is the Prototype Chain?
The prototype chain is a linked series of objects that JavaScript uses for property lookup. Every object can have a prototype, and that prototype can have its own prototype, forming a chain.
- An object stores its own properties directly.
- If a property is missing, JavaScript searches the prototype.
- If it is still missing, the search continues up the chain.
- The chain ends at null.
This is the foundation of JavaScript inheritance. It is also why arrays have methods like push() and strings can use methods like slice() even though those methods are not stored on each individual value.
2. Why the Prototype Chain Matters
The prototype chain is important because it explains how JavaScript objects share behavior without copying methods into every instance. That keeps code memory-efficient and consistent.
It also matters when you are debugging. A property may appear to “exist” on an object even though it is inherited from a prototype. Likewise, a property lookup may fail because the property is not on the object you think it is.
3. Basic Syntax or Core Idea
Property lookup flow
JavaScript does not search randomly. It follows a predictable order: first the object itself, then its prototype, then the prototype’s prototype, and so on.
const user = { name: "Ava" };The object above has its own name property. If you ask for a property that is not there, JavaScript will look at the prototype chain next.
Inspecting a prototype
const proto = Object.getPrototypeOf(user);This returns the prototype object that user delegates to for missing properties.
Creating an object with a known prototype
const animal = {
speak() {
return "Some sound";
}
};
const dog = Object.create(animal);
dog.name = "Milo";Here, dog inherits speak() from animal. The method is not copied; it is found through the prototype chain.
4. Step-by-Step Examples
Example 1: Looking up an own property
When a property exists directly on the object, JavaScript stops immediately and uses it.
const book = {
title: "Clean Code"
};
console.log(book.title);The lookup ends on book itself because title is an own property.
Example 2: Falling back to the prototype
Now compare that with a property that is inherited.
const person = {
greet() {
return "Hello";
}
};
const admin = Object.create(person);
admin.role = "editor";
console.log(admin.greet());admin does not define greet(), so JavaScript finds it on person.
Example 3: Built-in objects use the prototype chain
Built-in objects also rely on prototypes. Array instances inherit methods from Array.prototype.
const items = [1, 2, 3];
console.log(items.map(n => n * 2));The map() method comes from the array prototype, not from the items array object itself.
Example 4: Checking whether a property is inherited
Use hasOwnProperty or Object.hasOwn when you need to know whether a property belongs directly to the object.
const vehicle = {
wheels: 4
};
const car = Object.create(vehicle);
car.brand = "Toyota";
console.log(Object.hasOwn(car, "brand"));
console.log(Object.hasOwn(car, "wheels"));This distinguishes direct properties from inherited ones, which is very useful during debugging.
5. Practical Use Cases
- Sharing methods across many objects without duplicating them.
- Understanding built-in behavior such as arrays, dates, and regular expressions.
- Checking whether a property is direct or inherited when validating data.
- Building constructor functions and classes that reuse shared behavior.
- Debugging why a property or method is available even though it was never assigned on the object itself.
6. Common Mistakes
Mistake 1: Assuming inherited properties are own properties
Beginners often see a property on an object and assume it belongs directly to that object. That can lead to incorrect validation logic.
Problem: The property exists on the prototype, so a simple property check can give misleading results if you expect only direct properties.
const settings = Object.create({ theme: "dark" });
if ("theme" in settings) {
console.log("theme exists");
}Fix: Use Object.hasOwn when you only want properties that are directly on the object.
const settings = Object.create({ theme: "dark" });
if (Object.hasOwn(settings, "theme")) {
console.log("own theme exists");
}The corrected version works because it checks only the object’s own properties, not inherited ones.
Mistake 2: Forgetting where methods actually live
It is easy to believe that methods like map() or toUpperCase() are stored on each value. In reality, they come from prototypes.
Problem: Trying to call a method on the wrong type often leads to errors such as TypeError: value.map is not a function because the object does not inherit that method.
const notAnArray = {
0: 1,
1: 2,
length: 2
};
notAnArray.map(n => n * 2);Fix: Use a real array when you need array methods, or convert array-like data first.
const values = [1, 2];
console.log(values.map(n => n * 2));The fixed version works because arrays inherit map() from Array.prototype.
Mistake 3: Mutating shared prototype objects by accident
If several objects share the same prototype object, changing a nested object on that prototype affects every object that inherits from it.
Problem: This is not a JavaScript syntax error, but it is a common design bug. One change can appear everywhere because all children reference the same prototype object.
const defaults = {
options: { debug: false }
};
const first = Object.create(defaults);
const second = Object.create(defaults);
first.options.debug = true;Fix: Put per-object state on the object itself, not on shared prototype data structures.
const defaults = {
log() {
console.log("debug mode");
}
};
const first = Object.create(defaults);
const second = Object.create(defaults);
first.debug = true;The corrected version keeps shared behavior on the prototype while storing instance-specific state directly on each object.
7. Best Practices
Practice 1: Use prototypes for shared behavior, not shared data
Methods are ideal on prototypes because they can be reused by many objects. Mutable data is usually better stored on each object individually.
const accountProto = {
deposit(amount) {
this.balance += amount;
}
};
const account = Object.create(accountProto);
account.balance = 100;This keeps behavior shared and state isolated.
Practice 2: Prefer Object.getPrototypeOf and Object.create for clarity
These built-in methods make your intent obvious and avoid relying on legacy prototype access patterns.
const base = { kind: "base" };
const child = Object.create(base);
console.log(Object.getPrototypeOf(child) === base);The code clearly expresses prototype relationships without needing low-level internals.
Practice 3: Use Object.hasOwn for direct-property checks
When validating user input or configuration objects, direct-property checks are safer and easier to reason about.
function readPort(config) {
if (Object.hasOwn(config, "port")) {
return config.port;
}
return 3000;
}This avoids accidentally accepting inherited properties from an unexpected prototype.
8. Limitations and Edge Cases
- Primitive values such as strings and numbers are temporarily wrapped when you access methods, so their behavior can still appear prototype-based.
- The prototype chain ends at null; trying to look beyond that is impossible.
- in checks inherited properties, while Object.hasOwn does not.
- Changing an object’s prototype at runtime is possible but usually discouraged because it can make code slower and harder to predict.
- Some built-in methods come from intermediate prototypes, not just one obvious top-level prototype, so the lookup path can be longer than expected.
- Custom objects created with Object.create(null) have no prototype chain at all, so they do not inherit toString(), hasOwnProperty(), or other common methods.
9. Practical Mini Project
Let’s build a tiny task item system using a shared prototype. Each task keeps its own state, while all tasks share the same behavior.
const taskPrototype = {
toggle() {
this.done = !this.done;
},
status() {
return this.done ? "done" : "open";
}
};
function createTask(title) {
const task = Object.create(taskPrototype);
task.title = title;
task.done = false;
return task;
}
const task1 = createTask("Write docs");
const task2 = createTask("Review PR");
task1.toggle();
console.log(task1.status()); // done
console.log(task2.status()); // openThis project shows the main idea of prototype-based reuse: one shared set of methods, many independent objects.
10. Key Points
- The prototype chain is JavaScript’s property lookup path for objects.
- JavaScript checks the object first, then walks up through prototypes until it reaches null.
- Shared methods usually belong on prototypes, not on each object instance.
- Object.hasOwn helps you distinguish own properties from inherited ones.
- Built-in objects use the same mechanism, which is why familiar methods are available on arrays, strings, and many other values.
11. Practice Exercise
Try this small exercise to reinforce prototype lookup and property ownership.
- Create a base object with a describe() method that returns a short sentence.
- Create two child objects using Object.create.
- Give each child its own name property.
- Call describe() on both children.
- Check which properties are own properties and which are inherited.
Expected output: both objects should use the shared describe() method, while each object keeps its own name.
Hint: Put the method on the base object, then assign instance-specific properties after creating each child.
Solution:
const base = {
describe() {
return `Name: ${this.name}`;
}
};
const first = Object.create(base);
first.name = "Alpha";
const second = Object.create(base);
second.name = "Beta";
console.log(first.describe());
console.log(second.describe());
console.log(Object.hasOwn(first, "describe"));
console.log(Object.hasOwn(first, "name"));12. Final Summary
The JavaScript prototype chain is how objects inherit behavior and how property lookup works behind the scenes. If a property is not found on an object, JavaScript continues searching through that object’s prototype chain until it either finds the property or reaches null.
Once you understand this lookup process, built-in objects become much easier to reason about, and your own object designs become clearer too. Use prototypes for shared methods, keep instance data on each object, and use Object.hasOwn when you need to tell direct properties apart from inherited ones.
Next, learn how constructor functions and classes build on the prototype chain so you can connect this concept to modern JavaScript object patterns.