ToolPopToolPop
JavaScript · Lesson 14 of 18

Prototype, the prototype chain, and what new actually does

7 min readUpdated 24 Jun 2026

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.

Diagram
rendering diagram...
  • 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.
js
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.

js
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 Order instances share one cancel function 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.

js
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.

Diagram
rendering diagram...

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.

js
const r = new RefundedUPI(500, "duplicate");
console.log(r instanceof RefundedUPI); // true
console.log(r instanceof UPIPayment);  // true
console.log(r instanceof Object);      // true

Predict 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.

js
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

0/3 answered
  1. 1.For `function F(){}; const f = new F();`, which is true?
  2. 2.What does the `new` keyword NOT do?
  3. 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?
common wrong answer:It always returns the new object

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?
A.__proto__ is the actual link on an instance pointing to its parent (the prototype object). prototype is a property on a constructor function that becomes the __proto__ of instances created with new. Every object has __proto__; only functions have prototype.
Q.What does the new keyword actually do?
A.Four steps: 1) creates a new empty object, 2) sets that object's __proto__ to Constructor.prototype, 3) runs the constructor with this bound to the new object, 4) returns the new object (unless the constructor explicitly returns a different object).
Chai0/2 done

Watching quietly. Tap me if you want a tip.

JS Playground
console
// hit run to see output

Try this (0 of 2 done)

  1. 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. 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());