JavaScript doesn’t have classical inheritance like Java or C++. Instead, it uses something called prototypal inheritance — objects inherit directly from other objects. This is one of the most important concepts in JS, and once it clicks, a lot of confusing behavior suddenly makes sense.
Every object has a prototype
Every object in JavaScript has a hidden internal link called [[Prototype]]. It points to another object — the object’s prototype. When we try to access a property that doesn’t exist on the object, JavaScript follows this link and looks for the property on the prototype. If it’s not there either, it goes to the prototype’s prototype, and so on. This chain of lookups is the prototype chain.
__proto__ vs .prototype
This is where most people get confused. Let’s clear it up:
__proto__(or[[Prototype]]) exists on every object. It’s the actual link to the object’s prototype. We should useObject.getPrototypeOf(obj)instead of__proto__directly (it’s deprecated but still works)..prototypeexists only on functions. It’s the object that will become the[[Prototype]]of instances created withnew.
function Person(name) {
this.name = name;
}
const manish = new Person("Manish");
// manish.__proto__ === Person.prototype (true)
// Person.prototype is the blueprint for instances
console.log(Object.getPrototypeOf(manish) === Person.prototype); // true
Think of it like this: Person.prototype is a stencil. When we use new Person(), the new object’s __proto__ gets pointed at that stencil.
How the prototype chain lookup works
When we access a property on an object, JavaScript does the following:
- Check the object itself — is the property there? If yes, use it.
- If not, check
object.__proto__(i.e., the prototype). - If still not found, check
object.__proto__.__proto__. - Keep going until we hit
null(end of the chain). - If the property isn’t found anywhere, return
undefined.
function Animal(type) {
this.type = type;
}
Animal.prototype.speak = function () {
return `${this.type} makes a sound`;
};
const dog = new Animal("Dog");
console.log(dog.type); // "Dog" — found on dog itself
console.log(dog.speak()); // "Dog makes a sound" — found on Animal.prototype
console.log(dog.toString()); // "[object Object]" — found on Object.prototype
Object.create()
Object.create() creates a new object with a specific prototype. It’s the cleanest way to set up prototype-based inheritance without constructor functions:
const parent = {
greet() {
return `Hello, I'm ${this.name}`;
}
};
const child = Object.create(parent); // child's [[Prototype]] is parent
child.name = "Manish";
console.log(child.greet()); // "Hello, I'm Manish"
We can also create an object with no prototype at all:
const bare = Object.create(null);
console.log(bare.toString); // undefined — no Object.prototype methods!
Property shadowing
When we set a property on an object that already exists on its prototype, the new property shadows (hides) the prototype’s version. The prototype property is still there — it’s just not reachable through this object anymore.
function Car(brand) {
this.brand = brand;
}
Car.prototype.honk = function () { return "Beep!"; };
const myCar = new Car("Toyota");
console.log(myCar.honk()); // "Beep!" — from prototype
myCar.honk = function () { return "HONK!"; }; // shadows the prototype method
console.log(myCar.honk()); // "HONK!" — own property now
delete myCar.honk; // remove the shadow
console.log(myCar.honk()); // "Beep!" — prototype method is back
hasOwnProperty vs in
inchecks the entire prototype chainhasOwnProperty()(or the modernObject.hasOwn()) checks only the object’s own properties
const obj = Object.create({ inherited: true });
obj.own = true;
console.log("own" in obj); // true
console.log("inherited" in obj); // true
console.log(obj.hasOwnProperty("own")); // true
console.log(obj.hasOwnProperty("inherited")); // false
End of the chain
The prototype chain always ends at Object.prototype, whose own [[Prototype]] is null:
console.log(Object.getPrototypeOf(Object.prototype)); // null
When JavaScript reaches null, the lookup stops. If the property wasn’t found anywhere in the chain, we get undefined.
In simple language, the prototype chain is how JavaScript shares behavior between objects. When we look up a property, JS walks up the chain of linked objects until it finds what it’s looking for (or hits null). Every object is linked to a prototype, and that’s how methods like toString() magically work on every object even though we never defined them.