Ad – 728Γ—90
πŸ—οΈ OOP

JavaScript Prototypes – The Prototype Chain Explained

Every JavaScript object has a hidden [[Prototype]] link to another object. When you access a property that doesn't exist on an object, the engine follows this chain until it either finds the property or reaches null. This prototype chain is the true engine of all inheritance in JavaScript β€” ES6 classes are merely friendly syntax on top of it. This lesson demystifies the chain with ASCII diagrams, code, and practical patterns.

⏱️ 21 min read 🎯 Intermediate πŸ“… Updated 2026

What Is [[Prototype]]?

Every object in JavaScript has an internal property called [[Prototype]] (double brackets indicate it's an engine-level slot, not a regular property). For regular objects created with {}, this slot points to Object.prototype. For instances created with new SomeClass(), it points to SomeClass.prototype.

JavaScript
const obj = { name: 'Alice' };

// Standard way to read [[Prototype]]
const proto = Object.getPrototypeOf(obj);
console.log(proto === Object.prototype);    // true

// __proto__ is the old, non-standard accessor (still works but deprecated)
console.log(obj.__proto__ === Object.prototype); // true

// Check if a property is own (on the object itself) vs inherited
console.log(obj.hasOwnProperty('name'));    // true  β€” own property
console.log(obj.hasOwnProperty('toString')); // false β€” inherited from Object.prototype

// Verify toString lives on Object.prototype
console.log(Object.prototype.hasOwnProperty('toString')); // true
β–Ά Output
true
true
true
false
true

The Prototype Chain Lookup Algorithm

When you access obj.someProperty, JavaScript follows these steps:

  1. Check if obj has an own property named someProperty β†’ if yes, return it.
  2. Follow [[Prototype]] to the next object in the chain.
  3. Repeat step 1–2 until the property is found or [[Prototype]] is null.
  4. If null is reached without finding the property, return undefined.
JavaScript
/*
  Chain visualisation (ASCII):

  rex (Dog instance)
  β”œβ”€ name: 'Rex'
  β”œβ”€ breed: 'Labrador'
  └─ [[Prototype]] β†’ Dog.prototype
                     β”œβ”€ bark()
                     └─ [[Prototype]] β†’ Animal.prototype
                                        β”œβ”€ eat()
                                        β”œβ”€ sleep()
                                        └─ [[Prototype]] β†’ Object.prototype
                                                           β”œβ”€ toString()
                                                           β”œβ”€ hasOwnProperty()
                                                           └─ [[Prototype]] β†’ null
*/

class Animal {
  eat()   { return `${this.name} is eating`; }
  sleep() { return `${this.name} is sleeping`; }
}

class Dog extends Animal {
  constructor(name, breed) {
    super();
    this.name  = name;
    this.breed = breed;
  }
  bark() { return `${this.name} says: Woof!`; }
}

const rex = new Dog('Rex', 'Labrador');

// Own property lookup
console.log(rex.hasOwnProperty('name'));   // true  β€” on rex itself

// One level up β€” Dog.prototype
console.log(rex.bark());                   // found on Dog.prototype

// Two levels up β€” Animal.prototype
console.log(rex.eat());                    // found on Animal.prototype

// Three levels up β€” Object.prototype
console.log(rex.toString());              // found on Object.prototype

// Not found anywhere β†’ undefined (not an error)
console.log(rex.fly);                     // undefined
β–Ά Output
true
Rex says: Woof!
Rex is eating
[object Object]
undefined

Function.prototype and Constructor.prototype

Every function object has a prototype property (not to be confused with [[Prototype]]). When you call a function with new, the resulting instance's [[Prototype]] is set to that function's prototype property.

JavaScript
function Greeter(name) {
  this.name = name;
}

// Methods added to .prototype are shared by all instances
Greeter.prototype.hello = function () {
  return `Hello, I'm ${this.name}`;
};

const g1 = new Greeter('Alice');
const g2 = new Greeter('Bob');

// Both instances share the SAME hello function (not copied)
console.log(g1.hello === g2.hello);       // true β€” shared reference!
console.log(g1.hello());
console.log(g2.hello());

// Instance's [[Prototype]] equals Greeter.prototype
console.log(Object.getPrototypeOf(g1) === Greeter.prototype); // true

// Greeter.prototype's [[Prototype]] is Object.prototype
console.log(Object.getPrototypeOf(Greeter.prototype) === Object.prototype); // true
β–Ά Output
true
Hello, I'm Alice
Hello, I'm Bob
true
true

Object.create() for Prototypal Inheritance

Object.create(proto) creates a new object whose [[Prototype]] is set to proto. This is the most direct way to set up prototypal inheritance without classes.

JavaScript
const vehicleProto = {
  start()  { return `${this.make} ${this.model} engine started`; },
  stop()   { return `${this.make} ${this.model} engine stopped`; },
  describe() { return `${this.year} ${this.make} ${this.model}`; }
};

function createVehicle(make, model, year) {
  const v = Object.create(vehicleProto);
  v.make  = make;
  v.model = model;
  v.year  = year;
  return v;
}

const car = createVehicle('Toyota', 'Camry', 2023);
console.log(car.describe());
console.log(car.start());
console.log(Object.getPrototypeOf(car) === vehicleProto); // true

// Null prototype β€” object with NO inherited properties
const pure = Object.create(null);
pure.key = 'value';
console.log(pure.key);           // value
console.log(pure.toString);      // undefined β€” no Object.prototype!
β–Ά Output
2023 Toyota Camry
Toyota Camry engine started
true
value
undefined

Own vs Inherited Properties

Method / OperatorSees own propsSees inherited propsNotes
hasOwnProperty(key)YesNoBest way to check own
Object.keys(obj)Yes (enumerable)NoMost common iteration
Object.getOwnPropertyNames(obj)Yes (all)NoIncludes non-enumerable
for...inYesYesIterates full chain
in operatorYesYes'toString' in obj is true
JSON.stringifyYes (enumerable)NoSafe for serialisation

Classes Are Syntactic Sugar Over Prototypes

ES6 classes don't introduce a new inheritance model. They are convenient syntax for setting up function constructors and linking .prototype chains. You can verify this:

JavaScript
class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a noise.`; }
}

class Dog extends Animal {
  speak() { return `${this.name} barks.`; }
}

const d = new Dog('Rex');

// Under the hood β€” prototype chain
console.log(typeof Dog);                          // 'function' β€” class IS a function
console.log(Dog.prototype.constructor === Dog);   // true
console.log(Object.getPrototypeOf(Dog) === Animal); // true  β€” static chain
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true β€” instance chain
console.log(d.speak());
β–Ά Output
function
true
true
true
Rex barks.
⚠️
Prototype Pollution (Security Warning)

Never allow user-controlled data to set properties via key paths like obj['__proto__']['admin'] = true. This is the prototype pollution attack: it mutates Object.prototype and affects every plain object in the application. Always sanitise keys and use Object.create(null) for lookup maps in security-sensitive code.

πŸ’‘
Use Object.hasOwn() in modern code

The newer Object.hasOwn(obj, key) is safer than obj.hasOwnProperty(key) because it works even on objects created with Object.create(null) (which have no inherited hasOwnProperty method). Available in Node 16+ and all modern browsers.

Ad – 336Γ—280

πŸ‹οΈ Practical Exercise

Without using the class keyword, create a prototype-based inheritance chain for Person β†’ Employee β†’ Manager:

  • Use function constructors and .prototype assignment for methods.
  • Set up the chain with Object.create(Person.prototype).
  • Add a method to each level and verify the full chain with Object.getPrototypeOf().
  • Then recreate the same hierarchy using ES6 class and confirm the runtime behaviour is identical.

πŸ”₯ Challenge Exercise

Write a function getFullChain(obj) that returns an array of all objects in the prototype chain of obj, from the object itself up to (but not including) null. Each entry should include the constructor name and a list of own method names. Test it on a three-level class hierarchy.

Interview Questions

  • What is the difference between __proto__ and Object.getPrototypeOf()?
  • What is at the top of every prototype chain in JavaScript?
  • What does Object.create(null) produce and when is it useful?
  • How do hasOwnProperty and the in operator differ?
  • Are ES6 classes a different inheritance system from prototypes?
  • What is prototype pollution and how can you prevent it?

πŸ“‹ Summary

  • Every object has a [[Prototype]] link; property lookups walk this chain until null.
  • Use Object.getPrototypeOf(obj) β€” not obj.__proto__ β€” to inspect the chain.
  • Object.prototype is at the top; it inherits from null.
  • hasOwnProperty / Object.hasOwn check only the object itself, not the chain.
  • Object.create(proto) creates objects with an explicit prototype β€” pure prototypal inheritance without classes.
  • ES6 classes are syntactic sugar; the runtime still uses the same prototype mechanism.
  • Prototype pollution is a security vulnerability β€” never allow arbitrary key writes to prototypes.

Frequently Asked Questions

What is the difference between prototype and [[Prototype]]? +

Function.prototype is a regular property on function objects that becomes the [[Prototype]] of instances created with new. [[Prototype]] is the internal slot on every object that forms the inheritance chain. They are related but distinct: Function.prototype is the source; [[Prototype]] is the link.

Can I change an object's prototype after creation? +

Yes, with Object.setPrototypeOf(obj, newProto). However, this is a very slow operation because the engine must invalidate its internal optimisations. Prefer setting the prototype at creation time via Object.create() or the class syntax.

Why is for...in sometimes dangerous? +

for...in iterates all enumerable properties including inherited ones. If someone has polluted a prototype (e.g., added a custom property to Object.prototype), it will appear in every for...in loop. Always guard with Object.hasOwn(obj, key) or use Object.keys() instead.