JavaScript Prototype Chain: How Object Inheritance Works

You never wrote map(), yet every array you create knows how to use it. Where does that method come from? The JavaScript prototype chain. It lets one object borrow properties and methods from another, which is how arrays, classes, constructor functions, and reusable object behavior all work without copying the same code into every object.
It is the fallback path JavaScript walks when an object doesn't have the property you asked for:
const animal = {
eats: true
};
const rabbit = Object.create(animal);
rabbit.name = "Snowball";
console.log(rabbit.name); // "Snowball" — own property
console.log(rabbit.eats); // true — inherited from animalrabbit has no eats property of its own. JavaScript finds it one step up, on animal, which is rabbit's prototype.
What You'll Learn About the Prototype Chain
- Why JavaScript uses prototype-based inheritance
- How property lookup travels through the prototype chain
- The difference between own and inherited properties
- How constructor functions, classes, and
Object.create()use prototypes - Common prototype chain mistakes and safer alternatives
Prerequisites for Learning JavaScript Prototypes
You'll follow this guide more easily if you know your way around objects and functions:
Want to check your fundamentals first? Try the JavaScript Objects Quiz.
What Is the JavaScript Prototype Chain?
The JavaScript prototype chain is the linked sequence of objects JavaScript searches when it resolves a property or method. The search starts at the current object, follows its internal [[Prototype]] links, and stops at null. That is the whole mechanism behind property delegation and prototype-based inheritance.
Ask an object for a property it doesn't have, and JavaScript follows the link to that object's prototype, then keeps walking until it finds the property or runs out of objects.
Picture a company help desk. You ask the person at the front desk a question. No answer? The question moves to their supervisor. Still no answer? It goes to a manager.
Each person is an object, and the path your question takes is the prototype chain. JavaScript starts at the object you touched and only moves up when the property is missing.
The first answer wins, so a lower-level object can give its own value instead of the one held higher up. If nobody has an answer, the search ends and you get undefined.
That last part is the idea worth holding onto, and it's what the rabbit example above shows: objects don't copy anything from their prototypes. They stay connected to them.
A prototype is nothing special: it's a regular object that another object falls back to during property lookup. Inheritance happens through that link, not by copying properties around.
Why Does the Prototype Chain Exist?
Prototypes exist so many objects can share the same behavior without each one carrying its own copy. Instead of duplicating methods, an object hands off any property request it cannot answer to another object. Take prototypes away and you'd be copying methods onto every object by hand, or writing your own delegation system.
Think about arrays. Every one of them needs map(), filter(), and push(). Stamping those functions into each new array would burn memory and turn a one-line bug fix into a hunt across thousands of copies.
JavaScript leans on prototypes because objects and functions sit at the center of the language. Shared methods live on a prototype; each object keeps only its own data. Update the shared method once, and every object that inherits it sees the change.
How Does the Prototype Chain Work?
Lookup happens one object at a time. JavaScript checks the original object, then follows its internal [[Prototype]] link, and repeats until it finds a match or hits null, at which point you get undefined.
Check the original object
JavaScript checks whether the object has its own property with the requested key.
Visit the object's prototype
If the property is missing, JavaScript follows the object's internal [[Prototype]] reference.
Continue through linked prototypes
Each prototype is checked in order. The first matching property wins.
Stop at null
The final prototype link is null. If no match was found, property access returns undefined.
Here's what that chain looks like for an array:
scores
↓ [[Prototype]]
Array.prototype
↓ [[Prototype]]
Object.prototype
↓ [[Prototype]]
nullCall scores.map(...) and JavaScript normally stops at Array.prototype. Something like toString sits farther up, on Object.prototype, so the search walks one link further to reach it.
Memorable rule: JavaScript checks locally first, then looks upward. The nearest matching property shadows properties with the same name higher in the chain.
Think Like the JavaScript Engine
It helps to trace what the runtime does when your code reads an inherited property. This lookup walks object references, which is a different job from resolving variable names, even though both involve searching.
- Execution context: The surrounding script or function is already running inside an execution context.
- Property evaluation: For an expression such as
user.describe, the engine evaluatesuserand checks the object for an owndescribeproperty. - Prototype traversal: If the property is absent, the engine follows
user's[[Prototype]]links until it finds the property or reachesnull. - Method call: If
user.describe()invokes a function, a new function execution context is pushed onto the call stack. thisbinding: For that method call,thisnormally refers touser, even whendescribewas inherited.- Memory: Objects and their prototypes stay connected through references. Engines may optimize repeated property lookups internally.
- Garbage collection: An object can be collected once nothing reaches it anymore. A prototype stays reachable while live objects still point at it.
Two chains, two jobs. The scope chain resolves variable names such as user. The prototype chain resolves properties such as user.name.
| Mechanism | Searches for | Follows |
|---|---|---|
| Scope chain | Variables and function declarations | Lexical environments |
| Prototype chain | Object properties and methods | [[Prototype]] links |
Understanding prototype, [[Prototype]], and __proto__
Three names that look alike and mean different things. This is where most confusion starts, so it's worth separating them:
[[Prototype]]is an object's internal link to another object.- A function's
prototypeproperty, such asUser.prototype, is used as the prototype of objects created withnew. __proto__is a legacy accessor that exposes[[Prototype]]; keep it out of application code.Object.getPrototypeOf()safely reads an object's prototype.Object.setPrototypeOf()changes a prototype, though doing so after an object exists can hurt performance.
The example below shows the first two working together: a method on User.prototype, and an instance that reaches it through its own [[Prototype]] link.
function User(name) {
this.name = name;
}
User.prototype.greet = function greet() {
return `Hello, ${this.name}`;
};
const user = new User("Mina");
console.log(user.greet()); // "Hello, Mina"
console.log(Object.getPrototypeOf(user) === User.prototype); // trueThe chain is:
user → User.prototype → Object.prototype → nullPrototype Chain Examples in JavaScript
With the vocabulary sorted out, here are the three patterns you'll meet most often in real code.
Sharing Methods with Constructor Functions
Put a method on a constructor's prototype and every instance gets it:
function ShoppingCart(owner) {
this.owner = owner;
this.items = [];
}
ShoppingCart.prototype.addItem = function addItem(item) {
this.items.push(item);
};
ShoppingCart.prototype.getTotalItems = function getTotalItems() {
return this.items.length;
};
const firstCart = new ShoppingCart("Ava");
const secondCart = new ShoppingCart("Noah");
firstCart.addItem("Keyboard");
console.log(firstCart.getTotalItems()); // 1
console.log(secondCart.getTotalItems()); // 0
console.log(firstCart.addItem === secondCart.addItem); // trueThat last line is the point: both carts hold separate items arrays, but they share one copy of each method.
Creating Delegation with Object.create()
Object.create() builds an object and sets its prototype in one step:
const accountActions = {
describe() {
return `${this.owner} has a ${this.type} account`;
}
};
const customerAccount = Object.create(accountActions);
customerAccount.owner = "Leah";
customerAccount.type = "savings";
console.log(customerAccount.describe());
// "Leah has a savings account"
console.log(Object.hasOwn(customerAccount, "describe")); // falsedescribe runs fine even though customerAccount doesn't own it. Nothing is hidden here, which is what makes this pattern good for showing the delegation relationship plainly.
How Classes Use the Prototype Chain
Class syntax reads better, but instance methods still land on a prototype:
class Vehicle {
move() {
return "The vehicle is moving";
}
}
class Bicycle extends Vehicle {
ringBell() {
return "Ring ring!";
}
}
const bicycle = new Bicycle();
console.log(bicycle.ringBell()); // "Ring ring!"
console.log(bicycle.move()); // "The vehicle is moving"
console.log(Object.getPrototypeOf(bicycle) === Bicycle.prototype); // true
console.log(Object.getPrototypeOf(Bicycle.prototype) === Vehicle.prototype); // trueclass and extends don't replace prototypes. They're syntax for building and wiring up prototype relationships.
How to Recognize Prototype Chains in Real Code
Once you know the patterns, you start spotting them everywhere. Prototype behavior shows up wherever code reads an inherited property or sets up an inheritance relationship:
- Constructor functions used with
new - Assignments such as
Constructor.prototype.method class,extends, andsuper- Objects created with
Object.create() - Calls to
Object.getPrototypeOf() - Methods used by arrays, dates, maps, sets, and other built-ins
- Libraries that define reusable methods for many instances
React class components extend React.Component. Custom error types extend Error. Even a plain items.filter() call rides the array prototype chain.
In practice, most production code touches prototypes through classes and built-in objects. Hand-editing prototype links is rare, and usually a smell.
Common Mistakes with the Prototype Chain
Three mistakes account for most prototype bugs. Each one is easy to make and easy to avoid.
Modifying Built-In Prototypes
Common mistake: Adding methods to Object.prototype or Array.prototype creates naming conflicts, changes behavior in unrelated code, and breaks libraries that inspect inherited properties.
// ❌ Wrong: affects nearly every ordinary object
Object.prototype.isAvailable = function isAvailable() {
return true;
};
// ✅ Correct: use a regular utility function
function isAvailable(value) {
return value !== null && value !== undefined;
}
console.log(isAvailable({})); // trueA plain function does the same work and touches nothing outside its own module.
Storing Mutable Instance Data on a Prototype
Put an array or object on a prototype and every instance shares that one value, unless an instance replaces it.
function Team(name) {
this.name = name;
}
// ❌ Wrong: shared by all Team instances
Team.prototype.members = [];
const designTeam = new Team("Design");
const engineeringTeam = new Team("Engineering");
designTeam.members.push("Kai");
console.log(engineeringTeam.members); // ["Kai"]Adding Kai to the design team also added them to engineering. Both teams were pushing into the same array. Create the array per instance instead:
function Team(name) {
this.name = name;
this.members = []; // ✅ Each team gets its own array
}
const designTeam = new Team("Design");
const engineeringTeam = new Team("Engineering");
designTeam.members.push("Kai");
console.log(designTeam.members); // ["Kai"]
console.log(engineeringTeam.members); // []Assuming Every Property Is Owned by the Object
A property can be readable without belonging to the object you're reading it from:
const permissions = {
canRead: true
};
const userPermissions = Object.create(permissions);
userPermissions.canWrite = false;
console.log("canRead" in userPermissions); // true
console.log(Object.hasOwn(userPermissions, "canRead")); // false
console.log(Object.hasOwn(userPermissions, "canWrite")); // truePick your check to match your intent. Use in when inherited properties should count, and Object.hasOwn() when only direct properties should.
Prototype Chain Best Practices
These habits keep you clear of the mistakes above.
Split behavior from state: keep shared, stateless methods on prototypes, and create mutable instance data such as arrays and objects inside constructors or class fields.
Set prototypes up front: reach for class, extends, constructor prototypes, or Object.create() rather than rewiring an existing object with Object.setPrototypeOf().
A few more worth keeping in mind:
- Use
Object.getPrototypeOf()instead of__proto__. - Use
Object.hasOwn(object, key)for ownership checks. - Keep inheritance chains short and readable.
- Reach for composition when objects have no clear "is-a" relationship.
- Leave built-in prototypes alone in shared application code.
Where Prototype Inheritance Is Used in Production
You've been relying on prototype chains all along. They sit under a lot of everyday JavaScript:
- Built-in collections: Arrays inherit methods from
Array.prototype. - Custom error types: Application errors can extend the built-in
Errorclass. - UI libraries: Class-based components and framework base classes use inheritance.
- Domain models:
AdminUsermay extend a generalUserclass. - Reusable services: Instances can share validation or formatting methods.
- Browser APIs: DOM elements inherit methods through several interface prototypes.
The pattern pays off most when many objects need the same stable behavior while holding their own separate state.
JavaScript Prototype Chain Interview Questions
Interviewers keep coming back to the same handful of questions. See how you'd answer these before reading the responses.
Frequently Asked Questions About Prototype Chains
Related JavaScript Topics and Quizzes
🔑 Key Takeaways
- The prototype chain lets objects delegate missing property requests to other objects.
- Property lookup starts on the current object and ends when a match or
nullis reached. - JavaScript classes and constructor functions both use prototype-based inheritance.
- Shared methods belong on prototypes; mutable instance data belongs on each instance.
- The scope chain resolves variables, while the prototype chain resolves object properties.
- Prefer
Object.getPrototypeOf()andObject.hasOwn()over legacy or fragile alternatives.
Test Your Prototype Chain Knowledge
You can now trace how JavaScript finds an inherited property, share methods across instances, read a class hierarchy as a prototype chain, and explain what happens when a lookup finds nothing. Time to put that against real code.