Every Object Has a Prototype
JavaScript is a prototype-based language. That sounds exotic, but the idea is simple: every object has a secret link to another object — its prototype — and when you ask for a property the object doesn't have, JavaScript follows the link and asks there.
rabbit doesn't have an eats property of its own. JavaScript checks rabbit, doesn't find it, follows the prototype link to animal, finds eats: true, and returns it. For flies, it walks the chain, finds nothing, and returns undefined.
This lookup-and-walk behaviour is the whole mechanism. Inheritance, methods, class — all of it is built on this.
The Prototype Chain
The chain doesn't stop at one step. A prototype can have its own prototype, and so on, until you hit null:
Run it and you'll see rabbit, then Object.prototype, then null. That's why rabbit.toString() works even though you never defined toString — it lives on Object.prototype, the top of almost every chain.
Property lookup walks this chain from the bottom up. Assignment, on the other hand, always writes to the object itself — it never reaches up. That asymmetry is important and surprises people regularly.
Constructor Functions and .prototype
Before class existed, the standard way to make many similar objects was a constructor function called with new:
Two things happen when you call new User("Ada"):
- A fresh object is created, with its prototype set to
User.prototype. Userruns withthisbound to that new object.
greet isn't copied onto each instance. It lives once on User.prototype, and both ada and boris find it by walking their chain. That's why the last line prints true — it's literally the same function.
prototype vs __proto__
These two names trip everyone up. They're related but not the same thing.
User.prototypeis a property on the constructor function. It's the object that becomes the prototype of instances made withnew User(...).ada.__proto__(orObject.getPrototypeOf(ada)) is the link on the instance pointing up to its prototype.
Prefer Object.getPrototypeOf(obj) over obj.__proto__ in new code. __proto__ is a legacy accessor kept around for compatibility; the function is the official API.
Classes Are Sugar Over This
Modern JavaScript lets you write class, but underneath, you're still dealing with prototypes. Compare the two versions side by side:
greet landed on User.prototype, same as if you'd written it by hand. The class keyword mostly gives you tidier syntax, stricter rules (you must use new), and a cleaner way to do extends — but the runtime model is identical.
Knowing this matters when you read error messages or debug this. An error about "User.prototype.greet" isn't a weird internal name — it's exactly where the method lives.
Inheritance Is Just Longer Chains
extends chains one prototype to another. The parent's prototype becomes the child's prototype's prototype:
Looking up rex.eat walks rex → Dog.prototype → Animal.prototype, finds eat there, and calls it with this still bound to rex. That's all extends does — it sets up the chain for you.
Creating Objects Directly With a Prototype
You don't need a constructor at all. Object.create(proto) makes a new object with the prototype you specify:
No class, no new, no constructor function. Two objects sharing one method through a shared prototype. This is the bare-metal form of prototypal inheritance — everything else is built on top of it.
hasOwnProperty: Own vs Inherited
Because lookups walk the chain, "foo" in obj returns true for inherited properties too. When you need to distinguish a property the object actually owns, use Object.hasOwn (or the older hasOwnProperty):
name is on the instance. greet is on the prototype. in finds both; Object.hasOwn only finds the first. This matters whenever you iterate with for...in or serialize an object — you usually want only the own properties.
Don't Monkey-Patch Built-In Prototypes
Because Array.prototype is shared by every array in your program, you could add methods to it:
// Please don't.
Array.prototype.last = function () {
return this[this.length - 1];
};
[1, 2, 3].last(); // 3
The problem isn't that it doesn't work — it does. The problem is that every library, every dependency, every future version of JavaScript now shares that namespace with you. When Array.prototype.last eventually ships as a real method with slightly different semantics, your code (or someone else's) breaks in subtle ways. The Array.prototype.flatten / Array.prototype.flat saga is the canonical cautionary tale.
Keep helpers as standalone functions:
One less shared surface to collide with.
The Mental Model
Strip everything else away, and prototypes come down to three rules:
- Every object has a prototype link (possibly
null). - Property reads walk up that chain; writes don't.
class,new, andextendsare ways of setting up those chains without typingObject.createyourself.
Keep those three in your head and the behaviour of this, instanceof, method resolution, and inheritance all fall into place.
Next: The Event Loop
Prototypes wrap up the object model. The next chapter moves to something completely different — how JavaScript actually runs your code over time. The event loop is what makes timers, promises, and async/await behave the way they do, and it's the foundation for everything async.
Frequently Asked Questions
What is a prototype in JavaScript?
Every JavaScript object has an internal link to another object called its prototype. When you access a property that isn't on the object itself, JavaScript walks up that link — the prototype chain — looking for it. That chain is how methods defined once get shared across many instances.
What's the difference between __proto__ and prototype?
prototype is a property on constructor functions (and classes). It's the object that will become the prototype of instances created with new. __proto__ (or Object.getPrototypeOf(obj)) is the actual link on an instance pointing to its prototype. So instance.__proto__ === Constructor.prototype.
Are JavaScript classes just syntactic sugar for prototypes?
Mostly, yes. class Foo { bar() {} } puts bar on Foo.prototype, exactly as if you'd written function Foo(){} and Foo.prototype.bar = function(){}. Classes add private fields, stricter semantics, and nicer syntax for extends and super, but the underlying machinery is still prototypes.
Should I add methods to built-in prototypes like Array.prototype?
Almost never. Modifying Array.prototype or Object.prototype affects every array or object in your program, including those from libraries. It can clash with future language additions and break for...in loops. Keep custom helpers in their own functions or modules.