JavaScript Prototypes, End to End.
JavaScript has no classes. Not really. The class keyword is theatre — a polite syntactic mask over the same mechanism that's powered the language since 1995: objects linking to other objects.
Every object has a parent
In JavaScript, every object has a hidden internal slot called [[Prototype]] — a pointer to another object. When you ask for a property, the engine looks at the object itself, and if it doesn't find it, follows that pointer. And follows the next. Until either it finds the property, or hits null.
Fig. 1 — When you call dog.toString(), the engine walks dog → Animal.prototype → Object.prototype until it finds it.
The four names for the same thing
[[Prototype]]
Internal slot. Every object has one. Points to another object (or null). The thing the engine actually walks.
__proto__
Legacy getter/setter exposing [[Prototype]]. Works in all browsers but deprecated.
.prototype
A property on functions only. It's the object that becomes [[Prototype]] of instances created with new.
Object.getPrototypeOf(obj)
The modern, correct way to read an object's prototype.
What happens when you declare them
Function and class declarations both create constructor functions. Class adds ergonomics and subtle behavior — but the underlying object structure is identical.
When you declare a function
function Dog(name) { this.name = name; } Dog.prototype.bark = function() { console.log(this.name + ' says woof'); };
[[Prototype]] pointing to Function.prototype. So functions inherit .call, .apply, .bind.Dog.prototype.[[Prototype]] points to Object.prototype. Has one property: constructor, pointing back to Dog.Dog.prototype will become [[Prototype]] of every new Dog() instance.What new Dog('Rex') actually does
// new Dog('Rex') is roughly equivalent to: const obj = Object.create(Dog.prototype); // 1. new object const result = Dog.call(obj, 'Rex'); // 2. run with this = obj return (typeof result === 'object' && result !== null) ? result : obj; // 3. return obj
When you declare a class
class Dog { constructor(name) { this.name = name; } bark() { console.log(this.name + ' says woof'); } }
The same three things happen. Dog is still a function. Dog.prototype still exists. bark is placed on Dog.prototype.
Side by side
Function form
- Callable WITHOUT
new(works badly) - Hoisted to top of scope
- Methods are enumerable
Class form
- MUST use
new(TypeError otherwise) - NOT hoisted (temporal dead zone)
- Methods are non-enumerable
The full chain of an instance
Once you create an instance, three objects exist in a triangle. The relationships between them are the source of nearly every prototype quiz question.
Fig. 2 — The triangle. Dog.prototype.constructor === Dog. The function and its prototype reference each other.
Why methods live on the prototype
const dogs = []; for (let i = 0; i < 10000; i++) { dogs.push(new Dog('dog' + i)); } // All 10,000 dogs share the SAME bark function: dogs[0].bark === dogs[9999].bark // true
If 10,000 dogs each carried their own copy of bark, that's 10,000 function objects in memory. With prototypes, there's one bark, shared.
Extending the chain
Inheritance is just adding another link to the prototype chain. extends automates what used to take three lines of manual prototype manipulation.
class Animal { constructor(name) { this.name = name; } eat() { console.log(this.name + ' eats'); } } class Dog extends Animal { constructor(name, breed) { super(name); this.breed = breed; } bark() { console.log('woof'); } }
Fig. 3 — Four objects, three links. Lookup walks left to right.
Where prototypes get weird
This is the section that costs people interviews. Each of these has fooled experienced engineers.
1. Shared mutable prototype properties
class Dog { toys = []; // instance field — OK } Dog.prototype.friends = []; // ⚠ shared by ALL dogs const rex = new Dog(); const max = new Dog(); rex.friends.push('spot'); console.log(max.friends); // ['spot'] — wat
Mutating friends through one instance mutates it for all. Instance fields are the safe pattern for mutable data.
2. Object.create(null) and the missing methods
const bare = Object.create(null); bare.toString(); // TypeError
Objects with no prototype chain. Useful for dictionaries to avoid prototype pollution.
3. Arrow functions break this in prototypes
class Dog { name = 'Rex'; bark = () => console.log(this.name); // instance method woof() { console.log(this.name); } // prototype method }
bark is per-instance with this bound. woof is on prototype but loses this when passed as callback.
4. instanceof walks the prototype chain — it can lie
arr instanceof Array // might be false across iframes! Array.isArray(arr) // ✓ the safe way
Mutating the chain or crossing realms breaks instanceof. This is why React leans on duck-typing.
5. for...in walks the entire chain — prototype pollution
const obj = { a: 1 }; Object.prototype.bad = 'gotcha'; for (const k in obj) console.log(k); // 'a', then 'bad'
This is a real security CVE class. Lodash and jQuery have shipped fixes for it. Use Object.keys, Object.hasOwn(obj, k), or Map.
6. Modifying built-in prototypes broke the web (Smoosh-gate)
TC39 wanted to add Array.prototype.flatten until they discovered MooTools had patched it years prior. Standardizing broke production sites. The committee renamed it to flat.
7. Object.setPrototypeOf is a perf cliff
V8 optimizes objects by tracking their "shape" (hidden class). Changing prototype at runtime invalidates all those optimizations. Use Object.create at construction time.
The interview answer
"JavaScript uses prototypal inheritance, not classical. Every object has an internal [[Prototype]] slot pointing to another object. Property lookup walks that chain until it finds the property or hits null. Functions have a .prototype property, and new creates instances whose [[Prototype]] points to it. class is syntax over the same machinery."
The mental model
- Three terms, one slot.
__proto__,[[Prototype]],Object.getPrototypeOf()..prototypeis unrelated. - Class is sugar, not magic.
typeof class Foo {} === 'function'. - Methods on prototype, mutable state on instance.
- Mutating prototypes is dangerous. Prototype pollution is a CVE class.
- Prefer composition over deep inheritance hierarchies.
Before you leave — how confident are you with this?
Your honest rating shapes when you'll see this again. No grades, no shame.
Comments
Loading comments…