Prototypes

Understand the prototype chain, how prototype-based inheritance works, and how JavaScript looks up properties along that chain.

Underneath classes, arrays, and every built-in object, JavaScript's object system is prototype-based: every object has an internal link to another object (its prototype), and that link is how inheritance actually works in the language, even when you write modern class syntax.

Every object has a prototype

Every JavaScript object has an internal, hidden link to another object called its prototype, accessible via Object.getPrototypeOf(obj) (or the less recommended obj.__proto__). This is different from the object's own properties — it's a separate object the current object can 'fall back' to.

When you create an object with a literal ({}), its prototype is automatically Object.prototype — the root object nearly everything in JavaScript ultimately inherits from, which is where common methods like .toString() and .hasOwnProperty() actually live.

Property lookup and the prototype chain

When you access a property on an object (obj.someProperty), JavaScript first checks if the object has that property directly (an 'own property'). If not, it checks the object's prototype. If the prototype doesn't have it either, JavaScript checks the prototype's own prototype, and so on — this sequence of linked objects is the 'prototype chain'.

This lookup continues until either the property is found, or the chain reaches null (the end of every prototype chain — Object.prototype's own prototype is null). If the property is never found, the result is undefined, not an error.

Prototype-based inheritance

This chain is precisely how inheritance works at the lowest level in JavaScript: if object B's prototype is object A, then B automatically has access to every property and method A has (and everything in A's own prototype chain), without B needing to copy any of that data itself.

Functions have a special .prototype property (distinct from the function's own prototype) that becomes the prototype of any object created by calling that function with new. This is the mechanism class syntax builds on top of: every method you define in a class body is actually placed on that class's .prototype object, shared by every instance rather than duplicated per instance.

Object.create and setting prototypes directly

Object.create(proto) creates a brand-new object whose prototype is explicitly set to proto, which is the most direct, explicit way to establish prototype-based inheritance without functions or classes at all.

Object.setPrototypeOf(obj, proto) can change an existing object's prototype after creation, though this is rarely used in practice (it's slow and considered bad practice) — Object.create() at creation time, or class/extends, are the preferred approaches.

Quick reference

obj.hasOwnProperty(key) tells you whether a property exists directly on obj, versus somewhere up the prototype chain. The in operator checks the entire prototype chain, unlike hasOwnProperty which checks only the object itself. Modifying a shared prototype's method affects every object that inherits from it — this is a feature (shared, memory-efficient methods), not a bug.

// every object literal's prototype is Object.prototype
const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true

// Object.create builds a custom prototype chain directly
const animal = {
  speak() { return `${this.name} makes a sound.`; }
};
const dog = Object.create(animal);
dog.name = "Rex";
console.log(dog.speak()); // "Rex makes a sound." — found via the prototype chain

// checking own vs inherited properties
console.log(dog.hasOwnProperty("name"));  // true  (own property)
console.log(dog.hasOwnProperty("speak")); // false (inherited via prototype)
console.log("speak" in dog);              // true  (found somewhere on the chain)

// constructor functions and .prototype
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  return `${this.name} makes a sound.`;
};
const cat = new Animal("Whiskers");
console.log(cat.speak()); // found on Animal.prototype, not on cat itself

Code Examples

car itself only has a 'brand' property, but it can still call .describe() and read .wheels because both are found by walking up the prototype chain to vehicle, which car's prototype points to. Expected result: This vehicle has 4 wheels. 4

Object.create(vehicle) makes vehicle the prototype of car, so car inherits vehicle's properties and methods.

This is the manual, pre-class way to implement inheritance: Dog.prototype is set to a new object whose prototype is Animal.prototype, chaining Dog's instances up to Animal's shared methods, while Dog's own speak() overrides Animal's version. Expected result: Rex barks. true true

Dog.prototype = Object.create(Animal.prototype); links Dog's prototype chain to Animal's prototype.

This traces an explicit three-level prototype chain (top -> middle -> base -> Object.prototype -> null), showing exactly how far JavaScript walks to resolve each property, and confirms where the chain terminates. Expected result: hello from base only on middle true true true null

Object.getPrototypeOf(Object.prototype) is null, marking the end of every prototype chain.

const vehicle = {
  wheels: 4,
  describe() {
    return `This vehicle has ${this.wheels} wheels.`;
  }
};

const car = Object.create(vehicle);
car.brand = "Toyota";

console.log(car.describe());
console.log(car.wheels);
function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  return `${this.name} makes a sound.`;
};

function Dog(name) {
  Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function () {
  return `${this.name} barks.`;
};

const rex = new Dog("Rex");
console.log(rex.speak());
console.log(rex instanceof Dog);
console.log(rex instanceof Animal);
const base = { greet() { return "hello from base"; } };
const middle = Object.create(base);
middle.middleOnly = "only on middle";
const top = Object.create(middle);
top.name = "top object";

console.log(top.greet());          // walks: top -> middle -> base
console.log(top.middleOnly);       // walks: top -> middle
console.log(Object.getPrototypeOf(top) === middle);   // true
console.log(Object.getPrototypeOf(middle) === base);  // true
console.log(Object.getPrototypeOf(base) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null — end of the chain

The prototype chain

Looking up myDog.speak() checks myDog itself (not found), then Dog.prototype (not found), then Animal.prototype (found!) and stops there. If nothing in the whole chain has the property, the result is undefined, not an error.

myDog  (own properties: name, breed)
    |  [[Prototype]]
    v
  Dog.prototype   (methods: bark)
    |  [[Prototype]]
    v
  Animal.prototype  (methods: speak, eat)
    |  [[Prototype]]
    v
  Object.prototype  (methods: toString, hasOwnProperty, ...)
    |  [[Prototype]]
    v
  null   <-- chain ends here

Important Rules

Every object has a prototype (except one created via Object.create(null)), forming a chain that ends at null.

Property lookup checks the object's own properties first, then walks up the prototype chain until found or the chain ends.

hasOwnProperty checks only the object itself; the in operator checks the whole chain.

class syntax (next lesson) is built entirely on top of this same prototype mechanism — it does not replace it.

Where this shows up

Every array method (.map, .filter, .push), every string method (.slice, .toUpperCase), and every method on a class instance is only accessible because of prototype chain lookup — none of these methods are copied onto each individual array, string, or object. Understanding this chain is essential for debugging 'why does calling this method not work' issues and for understanding what class syntax is actually doing underneath.

if ("speak" in dog) { /* assume dog defines its own speak */ } if (dog.hasOwnProperty("speak")) { /* dog itself defines speak, not inherited */ }

The in operator returns true for inherited properties too, not just own ones — use hasOwnProperty when you specifically need to know if a property lives directly on that object.

Animal.prototype.speak = function() {}; // shared method mutated after many instances already exist

Define all shared prototype methods once, before creating instances, and avoid mutating a prototype at runtime unless you fully intend every existing instance to be affected.

Because all instances share the same prototype object by reference, changing a prototype method later changes it for every existing and future instance simultaneously, which is rarely the intended behavior and is a common source of subtle bugs.

[Multiple Choice] If an object does not have a property directly, and none of the objects in its prototype chain have it either, what does accessing that property return? A. A ReferenceError is thrown B. null C. undefined D. 0

C. undefined — Accessing a missing property (after checking the entire prototype chain and finding nothing) simply evaluates to undefined — it does not throw an error.

[True / False] hasOwnProperty and the in operator always return the same result for a given object and property name. A. True B. False

B. False — False. in checks the entire prototype chain (returns true for inherited properties too), while hasOwnProperty checks only the object's own, direct properties — they can disagree for inherited properties.

Using Object.create, build a 'shape' object with an area() method that returns 0, then create a 'circle' object whose prototype is 'shape', give it a radius property, and override area() to compute pi * radius^2.

const shape = { area() { return 0; } }; const circle = Object.create(shape); circle.radius = 5; circle.area = function () { return Math.PI * this.radius ** 2; }; console.log(circle.area()); // ~78.54

Object.create(shape) sets shape as circle's prototype; defining circle.area directly shadows (overrides) the inherited version.