Prototype, the prototype chain, and what new actually does
JavaScript does not have classes. It has objects pointing to other objects. The class keyword is a polite lie told to make Java and C# developers feel at home.
Once you internalise the chain, every "how does inheritance work in JS" interview question becomes a one-liner.
The chain, in pictures
Every object has an internal slot called [[Prototype]]. You read it via __proto__ (legacy) or Object.getPrototypeOf(obj) (modern). When you access a property, JS walks the chain.
- Step 1: Look on the object itself (own properties).
- Step 2: If not found, look on
__proto__. - Step 3: Keep climbing until you hit
null. - Step 4: If still not found, return
undefined.
const swiggyOrder = { item: "Biryani" };
console.log(swiggyOrder.toString);toString does not live on swiggyOrder. JS walks up to Object.prototype, finds it there, returns the function reference. That is the whole magic.
What new actually does
When you write new Order("BIR-42"), four things happen. Memorise these. They come up in every senior round.
function Order(id) {
this.id = id;
}
Order.prototype.cancel = function () {
return `cancelled ${this.id}`;
};
const o = new Order("BIR-42");
console.log(o.cancel());Step 1: A fresh empty object is created.
Step 2: That object's __proto__ is linked to Order.prototype.
Step 3: Order is called with this bound to the new object.
Step 4: If the constructor returns an object, that is used; otherwise the new this is returned.
So o.__proto__ === Order.prototype is true. The cancel method is not copied onto o. It lives on Order.prototype and is looked up via the chain.
Interview trap: candidates think methods are duplicated per instance. They are not. One thousand
Orderinstances share onecancelfunction in memory. Defining methods inside the constructor (this.cancel = function () {...}) breaks this and creates a new function per instance. Do not do it.
class is sugar
Modern syntax. Same machinery underneath.
class UPIPayment {
constructor(amount) { this.amount = amount; }
format() { return `rupees ${this.amount}`; }
}
class RefundedUPI extends UPIPayment {
constructor(amount, reason) {
super(amount);
this.reason = reason;
}
format() { return `${super.format()} (refunded: ${this.reason})`; }
}UPIPayment.prototype.format is the function. RefundedUPI.prototype has __proto__ pointing to UPIPayment.prototype. super.format() walks up the chain to call the parent's version.
Step 1: refund.format() lookup hits RefundedUPI.prototype.format first.
Step 2: Inside, super.format() looks up UPIPayment.prototype.format.
Step 3: Both run, the strings concatenate.
Senior rule: there is no class system. There are objects pointing to other objects.
Instance check, the honest version
instanceof does not check types. It walks the prototype chain looking for Constructor.prototype.
const r = new RefundedUPI(500, "duplicate");
console.log(r instanceof RefundedUPI); // true
console.log(r instanceof UPIPayment); // true
console.log(r instanceof Object); // truePredict output: all three are true. Because RefundedUPI.prototype chains up to UPIPayment.prototype which chains up to Object.prototype.
The extends Array gotcha
Subclassing built-ins works, but there are sharp edges. Methods like map and filter use Symbol.species to decide what to return. Without care, you get back a plain Array instead of your subclass.
class OrderList extends Array {
totalValue() {
return this.reduce((s, o) => s + o.price, 0);
}
}
const list = new OrderList({ price: 200 }, { price: 350 });
const cheap = list.filter(o => o.price < 300);
console.log(cheap instanceof OrderList);In modern engines this is true by default. But if any library along the chain overrides Symbol.species to return Array, you silently lose totalValue. The fix is to either override Symbol.species yourself or avoid subclassing built-ins for production data.
Interview trap: "Should I subclass Array?" The senior answer is: only if you accept that the contract is fragile, and you have tests proving your engine returns the subclass.
Prototype vocabulary
- [[Prototype]]
- The internal slot every object has, pointing to another object or null. Accessed via __proto__ or Object.getPrototypeOf.
- prototype
- A property on functions. When called with new, the new object's [[Prototype]] is set to this.
- super
- In a class method, calls the same-named method one step up the prototype chain.
- Symbol.species
- A well-known symbol that lets subclasses control what constructor built-in methods like map and filter use for the return value.
Closing
If someone hands you a JS codebase and asks "where does this method live", your answer should be a chain walk, not a guess. Open the object in DevTools, expand [[Prototype]], climb until you find it.
That is the entire object model. Two slots, one chain, no classes.
Quick quiz, prove you got it
- 1.For `function F(){}; const f = new F();`, which is true?
- 2.What does the `new` keyword NOT do?
- 3.Methods defined on the prototype are...
Interview insights
When asked "what does new do?"
- Create new empty object
- Set its __proto__ to Constructor.prototype
- Run the constructor function with this bound to the new object
- Return the new object (unless the constructor returns a non-primitive)
takeaway: Class syntax is just sugar over this four-step dance.
Tricky questions they will ask
Q.If your constructor returns an object, what does new return?›
A.It returns the object you returned, not the newly-created one. If you return a primitive (number, string, null), it is ignored and the new object wins. This is a real, rarely-asked-but-deadly trap.
Q.Why does extending built-ins like Array sometimes break?›
A.Because some methods return new arrays via Array (not via your subclass). Modern transpilers and engines handle most cases, but Symbol.species and some weird Array semantics can bite. Prefer composition over extending built-ins.
Free tools you can use while you learn
Common questions
Q.What is the difference between __proto__ and prototype?›
Q.What does the new keyword actually do?›
Watching quietly. Tap me if you want a tip.
Try this (0 of 2 done)
- 1
Verify that c.__proto__ is Customer.prototype.
show answer
function Customer(name) { this.name = name; } const c = new Customer('Ishita'); console.log(c.__proto__ === Customer.prototype); - 2
Create a class PremiumCustomer that extends Customer, override greet to say "Hi (premium), name".
show answer
class Customer { constructor(n) { this.name = n; } greet() { return 'Hi, ' + this.name; } } class PremiumCustomer extends Customer { greet() { return 'Hi (premium), ' + this.name; } } console.log(new PremiumCustomer('Aarav').greet());