Prototypes
How JavaScript objects share behavior with each other behind the scenes.
What is it?
When you call "hello".toUpperCase(), you're using a method you never defined yourself. Where did it come from? Every object in JavaScript has a hidden link to another object it can "fall back to" when it doesn't have a property itself — that fallback object is called its prototype.
If JavaScript can't find a property directly on an object, it checks the object's prototype, then that prototype's prototype, and so on, until it finds the property or runs out of links. This chain is called the prototype chain.
Explain like I'm 10
Imagine asking a coworker a question. If they don't know the answer, they ask their manager. If the manager doesn't know, they ask their manager. Each object checks its own knowledge first, then defers up a chain until someone has the answer.
Examples
Objects sharing behavior via a prototype
const animal = {
speak() {
console.log(this.name + " makes a sound.");
},
};
const dog = Object.create(animal);
dog.name = "Rex";
dog.speak(); // "Rex makes a sound."
// dog doesn't have its own "speak" method — it found it on "animal"class syntax uses prototypes underneath
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + " makes a sound.");
}
}
const cat = new Animal("Whiskers");
cat.speak(); // "Whiskers makes a sound."
console.log(Object.getPrototypeOf(cat) === Animal.prototype); // truespeak is defined once, on Animal.prototype, and every instance created with new Animal(...) shares that same method through the prototype chain — it isn't copied per instance. Note: Animal.prototype is a special property that only functions and classes have — it's the template object that becomes the internal fallback link (what Object.getPrototypeOf reads back) for every instance created with new Animal(...). They're two names for closely related things, not the same thing.
How it works
Every object has an internal link (accessible via Object.getPrototypeOf) pointing to another object. Property lookup checks the object itself first; if not found, it walks up this chain of prototypes. Arrays and functions are also objects, and they get useful built-in methods (like .map() or .call()) this exact same way — from their own prototypes.
Don't confuse this internal link with the .prototype property you see on functions and classes (like Animal.prototype) — that property is only a template object, used to set up the internal link on every instance created with new. Plain objects (like {}) don't have a .prototype property at all, even though they still have an internal prototype link.
Why does it exist?
Prototypes let many objects share the same methods without each one storing its own separate copy — saving memory and letting you update shared behavior in one place. It's the mechanism underneath JavaScript's classes and built-in types like arrays and strings.
When to use it
You lean on prototypes — often without realizing it — any time you call a built-in method on a value (.map(), .toUpperCase()). You reach for them directly when you want several objects to share the same behavior without duplicating it, which today is usually written with class rather than Object.create by hand.
When not to use it
For most everyday application code, you don't need to manipulate prototypes directly — class syntax covers the common cases more clearly. Reach for raw prototype manipulation only when you're building a library, working with older code, or need behavior class doesn't offer directly.
Common mistakes
Confusing an object's own properties with properties it only has access to through its prototype.
Modifying a shared prototype directly and accidentally affecting every object that relies on it.
Assuming JavaScript's
classsyntax is a completely different system — it's mostly a friendlier way to write prototype-based code.
Practice exercises
- Easy:
Use
Object.getPrototypeOf()on an array and on a plain object, and compare the results. - Medium:
Create an object
vehiclewith ahonkmethod, then useObject.create()to make acarobject that inherits it. - Hard:
Explain, using the prototype chain, why
[].toString()works even though arrays don't definetoStringthemselves.
Interview questions
What is the prototype chain?
A series of linked objects that JavaScript walks through when a property lookup fails on the object itself — first checking the object's own prototype, then that prototype's own prototype, and so on, until the property is found or the chain ends at null.
What actually triggers JavaScript to look at an object's prototype at all?
A property lookup that fails to find the property directly on the object — as an own property. Prototype lookup is a fallback that only kicks in after checking the object itself comes up empty.
What's the difference between `.prototype` and `__proto__`?
.prototype is a regular property that only functions and classes have — it's the template object handed to every instance created with new. __proto__ is the (legacy, informal) way to access any object's actual internal prototype link — the one Object.getPrototypeOf reads. They're related but not interchangeable: a function has both, while a plain object only has the internal link, not a .prototype property.
How does `class` relate to prototypes under the hood?
JavaScript classes are largely syntax sugar over the same prototype-based system that existed before them — methods written in a class body end up as properties on the class's .prototype object, shared by every instance, exactly the way manually assigning to Ctor.prototype would work.
What does `Object.create(proto)` do?
It creates a brand-new, empty object whose internal prototype link is set directly to proto, giving that new object access to everything on proto through the chain — without copying any of proto's properties onto it.
How does `new Ctor()` set up an instance's prototype link, compared to `Object.create(Ctor.prototype)`?
They end up doing almost the same thing to the link — new Ctor() creates a new object whose prototype is Ctor.prototype, then runs Ctor with this bound to that new object. Object.create(Ctor.prototype) only does the first part; it skips running the constructor function entirely, so any setup logic inside it (like assigning instance properties) never happens.
Predict the output: `const dog = Object.create(animal); dog.name = "Rex"; console.log(dog.hasOwnProperty("name"), dog.hasOwnProperty("speak"));` (where `animal` has a `speak` method).
true false. name was assigned directly onto dog, so it's an own property. speak is only reachable through the prototype chain — dog doesn't own it, it just has access to it.
What's the difference between `hasOwnProperty(key)` and the `in` operator?
obj.hasOwnProperty(key) only checks the object's own properties, ignoring anything it merely inherits through the chain. key in obj checks the entire chain — it returns true for both own properties and inherited ones.
Why does a `for...in` loop sometimes iterate over more keys than you expect, and how do you guard against it?
for...in walks the whole prototype chain and includes any inherited enumerable property, not just the object's own keys. Guarding with if (obj.hasOwnProperty(key)) inside the loop (or just using Object.keys(obj) instead, which only returns own enumerable keys) avoids picking up inherited ones accidentally.
Is `__proto__` still the recommended way to read or change an object's prototype?
No — it's a legacy accessor kept mainly for backward compatibility. The standard, recommended equivalents are Object.getPrototypeOf(obj) to read the link and Object.setPrototypeOf(obj, proto) to change it.
Why would you deliberately create an object with `Object.create(null)` instead of `{}`?
Object.create(null) produces an object with no prototype at all — not even Object.prototype — so it has none of the usual inherited methods like toString or hasOwnProperty. That's useful when you want a truly clean dictionary/map-like object with zero risk of an inherited property name colliding with a key you store in it.
If you modify a shared prototype after some instances already exist, do those existing instances see the change?
Yes — property lookup happens live, at the moment you access a property, not once at object-creation time. Since every instance's prototype link points to the very same prototype object, adding or changing a method on it becomes visible to every instance immediately, past and future.
What's a risk of assigning a brand-new object to `Ctor.prototype` wholesale (`Ctor.prototype = {...}`) instead of adding methods individually?
The new object doesn't automatically get a constructor property pointing back to Ctor the way the original auto-generated .prototype object did, which can silently break code that relies on instance.constructor to identify what created the object — you'd need to manually reassign constructor on the replacement object.
How does `instanceof` actually work, mechanically?
obj instanceof Ctor walks obj's prototype chain, checking at each link whether it's the exact same object as Ctor.prototype — it returns true the moment it finds a match, and false if it reaches the end of the chain without one.
Why do plain arrays have access to methods like `.map()` and `.forEach()` that you never defined yourself?
Every array's internal prototype link points to Array.prototype, which is where all of those built-in methods actually live, shared by every array. It's the exact same mechanism as a custom object inheriting a method from a hand-written prototype.
What's the risk of adding a method directly onto `Array.prototype` ("monkey-patching" a built-in)?
The new method becomes visible on every array in the entire program, including ones from other libraries, and can silently collide with a method a future JS version or another library adds with the same name — it affects global, shared state rather than being scoped to your own code.
What is "prototype pollution", and why is it treated as a security concern?
It's when untrusted input is used to set a property like __proto__ on an object being built (often through a naive recursive merge of user-supplied JSON), which actually reaches into and modifies Object.prototype itself — silently changing behavior for every plain object in the whole program, including ones with no obvious connection to the attacker's input.
Predict the output: two different objects are both created with `new Animal("x")`. Is `animalOne.speak === animalTwo.speak`?
true — both instances share the exact same function reference through their common prototype, Animal.prototype; the method isn't copied per instance, so comparing it by reference across two different instances gives equality.
If a constructor assigns `this.name = name` inside itself, does `name` end up on the instance or on the prototype?
On the instance — it becomes an own property of that specific object. Only things defined directly on Ctor.prototype (like methods in a class body) are shared through the chain; anything assigned to this inside the constructor is unique per instance.
How does `Object.keys(obj)` differ from a `for...in` loop in terms of what it returns?
Object.keys(obj) returns only the object's own enumerable property names, ignoring the entire prototype chain. for...in includes both own and inherited enumerable properties, which is why it's easy to get more keys back than you expected.
Predict the output: `const bare = Object.create(null); console.log(bare.toString);`
undefined — bare has no prototype at all, so it doesn't inherit toString (or anything else) from Object.prototype the way a normal {} would.
In a class, where do `static` methods actually live — on the prototype, alongside instance methods?
No — static methods are attached directly to the class (constructor function) itself, not to Ctor.prototype. That's why they're called on the class, like MyClass.staticMethod(), and are never accessible on an individual instance.
Can getters and setters be shared across instances the same way regular methods are?
Yes — a getter/setter defined in a class body (with get/set) is installed on the class's prototype just like a regular method, so every instance shares the same accessor logic, even though each instance's underlying data can differ.
Conceptually, how does JavaScript's prototypal inheritance differ from classical, class-based inheritance in languages like Java?
Classical inheritance defines a fixed class hierarchy at compile time, and every instance is a stamped-out copy following that blueprint. Prototypal inheritance links live objects directly to other live objects at runtime — there's no separate "class" concept underneath; class syntax in JS is just a friendlier way to set up those same object-to-object links.
When a subclass method calls `super.method()`, where does that lookup actually go?
It looks up method starting from the parent class's prototype specifically, skipping the subclass's own prototype (even if the subclass overrode method) — this lets an overriding method call the original version it's replacing without infinite recursion.
Why is repeatedly calling `Object.setPrototypeOf(obj, newProto)` on already-existing objects generally discouraged, beyond just being unusual?
Changing an object's prototype after creation is a slow operation in most JS engines — it can de-optimize code that was compiled assuming that object's "shape" (including its prototype) would stay stable, hurting performance far more than setting the prototype once up front via Object.create or a constructor.
Predict the output: `console.log(Object.getPrototypeOf(Object.getPrototypeOf({})));`
null. A plain object's prototype is Object.prototype, and Object.prototype's own prototype is null — the very end of the chain.
What sits at the very top of a normal object's prototype chain, and what comes after it?
Object.prototype sits at the top of most chains, supplying things like toString and hasOwnProperty. Its own prototype is null, which is what actually stops the chain — property lookup gives up and returns undefined once it reaches null.
Does an arrow function have a `.prototype` property the way a regular function does?
No — arrow functions never get an automatically-created .prototype property, which is one reason (along with not having their own this) that they can't be used as constructors with new.
If you declare a regular function but never call it with `new`, does it still have a `.prototype` property?
Yes — every regular (non-arrow) function automatically gets a .prototype property the moment it's created, whether or not it's ever actually used as a constructor; it just sits there unused if new is never applied to that function.
If an object has its own property shadowing a prototype property of the same name, and you `delete` the own property, does the prototype's version reappear?
Yes — delete only removes the object's own property. Since the prototype link itself was never touched, the next lookup of that same key falls through to the (never-removed) property on the prototype, exactly as if the own property had never been added.
Why is `obj.hasOwnProperty(key)` sometimes considered risky to call directly, and what's the safer alternative?
It relies on obj having actually inherited hasOwnProperty from Object.prototype — which fails on an object created with Object.create(null), or one where hasOwnProperty was itself overwritten as an own property. Object.prototype.hasOwnProperty.call(obj, key), or the newer Object.hasOwn(obj, key), calls the real check directly without depending on what obj happens to have inherited.
How does `Object.assign(target, source)` differ from having `target` inherit from `source` via the prototype chain?
Object.assign performs a one-time copy of source's own enumerable properties onto target — after that copy, the two objects are completely independent, and a later change to source has no effect on target. Prototypal inheritance instead creates a live, ongoing link: a later change to the prototype is immediately visible to everything that inherits from it.
Debugging: a teammate adds a new method to a shared prototype partway through the program's execution, expecting only future instances to be able to use it. What actually happens?
Every existing instance gains access to the new method too, since prototype lookup is resolved live at call time rather than copied in when the instance was originally created — there's no way to add a prototype method that only affects instances created afterward.