Prototypes and Prototype Chain

· 6 min read · Updated March 11, 2026 · intermediate
javascript prototypes inheritance es6

Every JavaScript object has a hidden link to another object called its prototype. This prototype chain is the foundation of inheritance in JavaScript, allowing objects to share behavior without duplicating code. Understanding prototypes gives you deep insight into how JavaScript actually works under the hood.

What Is a Prototype?

When you create an object in JavaScript, that object automatically gets a property called its prototype. Think of the prototype as a fallback object. When you try to access a property that does not exist on the object itself, JavaScript looks for it on the prototype, then on the prototype’s prototype, and so on down the chain.

This sounds abstract, but you have been using prototypes all along. When you call .toUpperCase() on any string, that method comes from its prototype:

const message = 'hello';
console.log(message.toUpperCase()); // 'HELLO'

The message string object has no toUpperCase method defined on it. JavaScript finds it by walking up the prototype chain to String.prototype, where toUpperCase lives.

The Prototype Chain

Every object chains back to Object.prototype, which has null at the end of the chain. Here is how to visualize it:

const arr = [1, 2, 3];

// Array.prototype
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true

// Object.prototype  
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true

// null - end of chain
console.log(Object.getPrototypeOf(Object.prototype)); // null

When you access a property, JavaScript searches in this order:

  1. The object itself (own properties)
  2. Its prototype
  3. The prototype’s prototype
  4. Continue until found or reach null

This is why it is called the prototype chain. Each object can delegate property lookups to its prototype, a pattern called prototypal inheritance.

Setting Prototypes with Object.create()

The most direct way to set an object is prototype is Object.create():

const parent = {
  greet() {
    return 'Hello, ' + this.name;
  }
};

const child = Object.create(parent);
child.name = 'Alice';

console.log(child.greet()); // 'Hello, Alice'
console.log(Object.getPrototypeOf(child) === parent); // true

The new object child has parent as its prototype. Methods defined on parent are available to child through delegation. This is different from copying methods. The methods live on parent and child simply references them.

You can also create objects with null as the prototype, giving you a completely empty object with no inherited properties:

const empty = Object.create(null);
console.log(empty.toString); // undefined

This is useful when you need a pure dictionary without any inherited behavior.

Reading and Setting Prototypes

Two methods give you full control over prototypes:

const obj = {};
const proto = { value: 42 };

// Get the prototype
console.log(Object.getPrototypeOf(obj)); // {}

// Set the prototype
Object.setPrototypeOf(obj, proto);

console.log(Object.getPrototypeOf(obj) === proto); // true
console.log(obj.value); // 42

Changing prototypes after object creation is possible but comes with performance costs. V8 and other engines optimize based on fixed prototypes, so modifying them later can de-optimize the object. If you are creating many objects, set the prototype once during creation.

The proto Property

Every object also has access to __proto__, a getter and setter that provides an older way to work with prototypes:

const parent = { x: 10 };
const child = {};

// Legacy but widely supported
child.__proto__ = parent;

console.log(child.x); // 10

This property existed before Object.getPrototypeOf and Object.setPrototypeOf were standardized. While still widely supported, __proto__ has quirks and is considered legacy. The modern methods are preferred for cleaner code.

One quirk: __proto__ in object literals has special behavior that differs from other properties:

// This creates __proto__ as a regular property, not the prototype!
const obj = {
  __proto__: { a: 1 },
  ownProp: 2
};

console.log(obj.a); // undefined - it is just a property!
console.log(obj.__proto__); // { a: 1 }

Constructor Functions and Prototype Property

Before ES6 classes, constructor functions were the standard way to create objects with shared methods:

function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  return this.name + ' makes a sound.';
};

const dog = new Animal('Dog');
const cat = new Animal('Cat');

console.log(dog.speak()); // 'Dog makes a sound.'
console.log(cat.speak()); // 'Cat makes a sound.'
console.log(dog.speak === cat.speak); // true - same function!

Every function gets a .prototype property automatically. When you call a function with new, the newly created object gets that function as its prototype. This is why all instances share the same methods. They are on the prototype, not duplicated on each instance.

You can verify this relationship:

function Cat(name) {
  this.name = name;
}

const kitty = new Cat('Kitty');

console.log(kitty.constructor === Cat); // true
console.log(Cat.prototype.constructor === Cat); // true

The .constructor property points back to the original function, which is useful for checking what type an object is.

Property Shadowing

When you define a property on an object that already exists on its prototype, the object own property takes precedence:

const parent = {
  value: 'parent'
};

const child = Object.create(parent);
console.log(child.value); // 'parent' - from prototype

child.value = 'child';  // Creates own property
console.log(child.value); // 'child' - shadows prototype
console.log(parent.value); // 'parent' - unchanged

delete child.value;  // Remove own property
console.log(child.value); // 'parent' - falls back to prototype

This is called shadowing. The own property shadows the prototype property. You can delete the own property to reveal the prototype version again.

Checking Prototype Relationships

JavaScript provides several ways to check these relationships:

const parent = { x: 1 };
const child = Object.create(parent);
child.y = 2;

console.log(child.hasOwnProperty('x')); // false - from prototype
console.log(child.hasOwnProperty('y')); // true - own property

console.log(child instanceof Object); // true
console.log(Object.prototype.isPrototypeOf(child)); // true

The hasOwnProperty method checks only the object own properties, ignoring the prototype chain. This is useful when you need to distinguish between an object own data and inherited behavior.

ES6 Classes and Prototypes

Modern JavaScript has class syntax, which builds on top of prototypes:

class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    return this.name + ' makes a sound.';
  }
}

const dog = new Animal('Dog');
console.log(dog.speak()); // 'Dog makes a sound.'

// Under the hood, this is still prototype-based!
console.log(Object.getPrototypeOf(dog) === Animal.prototype); // true

The class syntax is syntactic sugar that makes the prototype-based inheritance cleaner to write and easier to read. The methods are still placed on the prototype.

Conclusion

Prototypes are the mechanism behind JavaScript inheritance system. Every object links to a prototype, and when you access a property, JavaScript walks up this chain until it finds the property or reaches the end. Understanding this gives you control over how objects share behavior.

Key takeaways:

  • Use Object.create() to set an explicit prototype
  • Object.getPrototypeOf() and Object.setPrototypeOf() are the modern way to manipulate prototypes
  • The __proto__ property works but is considered legacy
  • Constructor functions with .prototype was the pre-class pattern
  • The prototype chain affects property lookups but not own property storage
  • Classes in ES6 are syntactic sugar over prototypes

This knowledge helps you debug issues, optimize performance, and understand why JavaScript behaves the way it does.