Prototypes
Every JavaScript object has an internal link to another object called its prototype. When you access a property that doesn't exist directly on an object, the engine automatically looks it up on the prototype — and if it's not there either, on the prototype's prototype, and so on.
🎯 What you'll learn
- What a Prototype Is
__proto__vsprototype— Two Different Things- Own Properties vs Inherited Properties
- Setting a Prototype Property Never Mutates the Prototype
Object.createvs Constructor Functions vsclass
1What a Prototype Is
Every JavaScript object has an internal link to another object called its prototype. When you access a property that doesn't exist directly on an object, the engine automatically looks it up on the prototype — and if it's not there either, on the prototype's prototype, and so on.
const animal = {
eats: true,
describe() { return "I am an animal"; },
};
const dog = Object.create(animal); // dog's prototype = animal
dog.barks = true;
console.log(dog.eats); // true — found on the prototype
console.log(dog.describe()); // "I am an animal" — inherited method
console.log(dog.barks); // true — own propertydog doesn't have eats or describe as its own properties — Object.create(animal) sets animal as dog's prototype, and lookups fall through to it.
2__proto__ vs prototype — Two Different Things
This is the single most confusing naming collision in JavaScript:
| Name | What it is | Exists on |
|---|---|---|
obj.__proto__ (or Object.getPrototypeOf(obj)) | The actual link to the object's prototype | every object |
Fn.prototype | The object that becomes the prototype of instances created with new Fn() | every function |
function Dog(name) { this.name = name; }
Dog.prototype.bark = function () { return `${this.name} says woof`; };
const rex = new Dog("Rex");
console.log(rex.__proto__ === Dog.prototype); // true
console.log(rex.bark()); // "Rex says woof"Dog.prototype is a plain object. new Dog(...) creates a new object whose __proto__ points at Dog.prototype. That's the entire mechanism behind "constructor functions."
3Own Properties vs Inherited Properties
console.log(rex.hasOwnProperty("name")); // true — set in the constructor
console.log(rex.hasOwnProperty("bark")); // false — inherited via the prototype
console.log("bark" in rex); // true — `in` checks the whole chainfor...in loops over both own and inherited enumerable properties, which is a common source of bugs — prefer Object.keys(obj) (own, enumerable only) when you just want the object's own data.
4Setting a Prototype Property Never Mutates the Prototype
dog.eats = false; // creates a NEW own property on dog
console.log(dog.eats); // false
console.log(animal.eats); // true — untouchedAssignment always creates/updates an own property; it never writes through to the prototype. Only reading falls through the chain — writing does not.
5Object.create vs Constructor Functions vs class
All three ultimately do the same thing — link one object's prototype to another:
// 1. Object.create — direct, explicit
const catA = Object.create(animal);
// 2. Constructor function — prototype set via Fn.prototype
function Cat() {}
Cat.prototype = animal;
const catB = new Cat();
// 3. class — syntactic sugar over the same mechanism
class Bird {}
Object.setPrototypeOf(Bird.prototype, animal);
const catC = new Bird();class (covered in its own topic) is the modern, readable way to do this — but it compiles down to exactly this prototype machinery.
1Why Prototypes Save Memory
Methods placed on a shared prototype exist once in memory, no matter how many instances are created — unlike defining a method inside the constructor, which creates a new function object per instance.
// BAD: new function created for every single Dog instance
function DogWasteful(name) {
this.name = name;
this.bark = function () { return `${this.name} says woof`; }; // duplicated per instance
}
// GOOD: one shared function on the prototype
function Dog(name) { this.name = name; }
Dog.prototype.bark = function () { return `${this.name} says woof`; };
const dogs = Array.from({ length: 10000 }, (_, i) => new Dog(`dog${i}`));
// Wasteful version: 10000 separate bark function objects
// Prototype version: 1 shared bark function, referenced by all 100002The Prototype Chain Terminates at null
console.log(Object.getPrototypeOf({})); // Object.prototype
console.log(Object.getPrototypeOf(Object.prototype)); // null — the end of the chainEvery plain object's chain eventually reaches Object.prototype (which provides toString, hasOwnProperty, etc.), then null.
const bare = Object.create(null); // no prototype at all — not even Object.prototype
console.log(bare.toString); // undefined — no inherited methods whatsoeverObject.create(null) is used for objects meant to be pure key-value dictionaries, immune to prototype pollution attacks.
3Overriding an Inherited Method
function Animal() {}
Animal.prototype.speak = function () { return "..."; };
function Cat() {}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.speak = function () { return "Meow"; }; // shadows Animal's version
const c = new Cat();
console.log(c.speak()); // "Meow"This is exactly how method overriding works in class-based inheritance too — class syntax just automates this wiring.
4Checking the Chain
console.log(rex instanceof Dog); // true — Dog.prototype is somewhere in rex's chain
console.log(Dog.prototype.isPrototypeOf(rex)); // true, equivalent check
console.log(Object.getPrototypeOf(rex) === Dog.prototype); // true, direct link onlyinstanceof walks the entire chain; Object.getPrototypeOf only gives you the immediate link.
⚠️ Common Pitfalls
- Mutating a shared prototype object accidentally —
Dog.prototype.tricks = []creates ONE array shared by everyDoginstance, not a per-instance array. Always initialize per-instance state (arrays, objects) inside the constructor. - Confusing
Fn.prototypewithObject.getPrototypeOf(instance)— they refer to the same object, but from different sides of the relationship; mixing them up is extremely common when debugging. - Using
for...inwithouthasOwnPropertychecks — iterates inherited enumerable properties too, which can pull in unexpected keys if the prototype has been extended. - Long prototype chains hurting lookup performance — every miss walks one more link; deeply nested inheritance (5+ levels) measurably slows property access in hot code paths.
- Reassigning
Fn.prototypeentirely after instances already exist — existing instances keep their old prototype link; only instances created after the reassignment get the new one.
📋 Quick Reference
instance.__proto__ === Constructor.prototype
instance.hasOwnProperty(key) → true only for own properties
key in instance → true for own OR inherited
Object.keys(instance) → own enumerable keys only
for...in instance → own AND inherited enumerable keys🎤 Interview Q&A
Q1What is a prototype in JavaScript?core▾
Every object has an internal link ([[Prototype]]) to another object; property lookups that fail on the object continue up this link. Constructors' prototype property becomes the prototype of instances created with new.
- __proto__ vs prototype?
prototypeis a property of functions;__proto__/Object.getPrototypeOfis an object's actual link.
Q2Why put methods on the prototype?core▾
All instances share one function instead of each instance holding its own copy, saving memory. Classes do this automatically for methods.
- What about class fields with arrow functions? They're per-instance.
Q3How does Object.create work?core▾
Object.create(proto) creates a new object whose prototype is proto, enabling prototypal inheritance without constructors; Object.create(null) creates an object with no prototype (a clean dictionary).
- Why use null-prototype objects? No inherited keys like
toStringinterfering.
Q4What is prototype pollution?senior▾
An attack where untrusted input sets properties like __proto__.isAdmin = true via unsafe merges, affecting all objects. Prevent with safe merge functions, Object.create(null), Map, schema validation, and freezing prototypes.
- Which operations are risky? Deep merge/clone of user-controlled JSON.
Q5How do you check own vs inherited properties?core▾
Object.hasOwn(obj, key) (or hasOwnProperty) for own properties; in checks the whole chain; Object.keys lists own enumerable properties.
- Why prefer Object.hasOwn? Works even when hasOwnProperty is shadowed or missing.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.