What Is the Prototype Chain in JavaScript and How Does Inheritance Work?

The prototype chain in JavaScript is a delegation mechanism where every object has an internal [[Prototype]] reference (accessible via Object.getPrototypeOf) that links to another object, forming a chain that the engine traverses upward to resolve property lookups until reaching Object.prototype whose prototype is null.

The leonardomso/33-js-concepts repository demonstrates these mechanics through comprehensive test suites. Understanding the prototype chain in JavaScript is fundamental to working with object-oriented patterns, as it governs how properties are inherited, how instanceof operates, and how memory is shared across instances.

Understanding the Core Prototype Chain Mechanism

Every JavaScript object contains an internal slot called [[Prototype]], exposed in code through Object.getPrototypeOf(obj) or the legacy __proto__ accessor. When you access a property or method, the engine first checks the object itself; if the property is missing, it follows the [[Prototype]] link to the next object and repeats the search. This continues recursively until the property is found or the chain terminates at Object.prototype (whose own prototype is null).

This linked structure is the prototype chain. It enables delegation-based inheritance rather than class-based copying, meaning objects inherit references to shared methods rather than holding separate copies.

When you declare class Child extends Parent, JavaScript sets up two distinct prototype relationships:

  1. Instance delegation: new Child() produces an object whose [[Prototype]] points to Child.prototype
  2. Prototype delegation: Child.prototype's [[Prototype]] points to Parent.prototype

As verified in tests/object-oriented/inheritance-polymorphism/inheritance-polymorphism.test.js (lines 3-10), this creates a three-tier chain for any Warrior instance extending Character:

Object.getPrototypeOf(warrior) === Warrior.prototype
Object.getPrototypeOf(Warrior.prototype) === Character.prototype
Object.getPrototypeOf(Character.prototype) === Object.prototype

Method Resolution Order

Property lookup follows this chain sequentially. The test suite (lines 15-21) confirms that Warrior.prototype.hasOwnProperty('attack') returns true because attack is defined directly on the child prototype, while Character.prototype.hasOwnProperty('introduce') returns true for inherited methods. If warrior.introduce() is called, the engine finds it on Character.prototype after failing to locate it on Warrior.prototype or the warrior instance itself.

The super Keyword and Static Inheritance

The super keyword provides explicit access to the parent class within subclass definitions. Inside constructors, super(args) invokes the parent constructor. Inside methods, super.method() climbs the prototype chain to execute the parent implementation.

The repository's ExtendedWarrior example (lines 50-60) demonstrates this pattern:

class ExtendedWarrior extends Character {
  attack() {
    const base = super.attack()  // Calls Character.prototype.attack
    return `${base} With an Axe!`
  }
}

Static Member Inheritance

Static methods and properties live on the constructor functions themselves, not on .prototype. When class Warrior extends Character is declared, JavaScript sets Object.getPrototypeOf(Warrior) === Character, enabling static inheritance. The test suite (lines 31-35) validates that Warrior.createRandom() works because it inherits the static createRandom method from Character via the constructor's prototype chain.

Checking Prototype Relationships with instanceof

The instanceof operator checks the entire prototype chain, not just the immediate constructor. As shown in the test file (lines 68-74), a Warrior instance returns true for warrior instanceof Warrior, warrior instanceof Character, and warrior instanceof Object because all three prototypes exist in the object's chain:

console.log(warrior instanceof Warrior)   // true
console.log(warrior instanceof Character) // true
console.log(warrior instanceof Object)    // true

This works by verifying whether Warrior.prototype (or any object in its prototype chain) exists in warrior's [[Prototype]] chain.

Practical Implementation Example

The following runnable code extracted from the repository demonstrates the complete inheritance pattern, including constructor delegation, method overriding, and polymorphic behavior:

// Base class definition
class Character {
  constructor(name, health = 100) {
    this.name = name
    this.health = health
  }
  
  introduce() {
    return `I am ${this.name} with ${this.health} HP`
  }
  
  attack() {
    return `${this.name} attacks!`
  }
  
  static createRandom() {
    const names = ['Hero', 'Villain', 'Sidekick']
    return new this(names[Math.floor(Math.random() * names.length)])
  }
}

// Subclass declaration
class Warrior extends Character {
  constructor(name) {
    super(name, 150)  // Delegates to Character constructor
    this.rage = 0
  }
  
  attack() {
    // Overrides parent method
    return `${this.name} swings a mighty sword!`
  }
  
  battleCry() {
    this.rage += 10
    return `${this.name} roars! Rage: ${this.rage}`
  }
}

// Prototype chain verification
const conan = new Warrior('Conan')
console.log(Object.getPrototypeOf(conan) === Warrior.prototype)           // true
console.log(Object.getPrototypeOf(Warrior.prototype) === Character.prototype) // true

// Property lookup demonstration
console.log(conan.attack())      // Uses Warrior.prototype.attack
console.log(conan.introduce())   // Uses Character.prototype.introduce (inherited)

// Static inheritance
const randomWarrior = Warrior.createRandom()
console.log(randomWarrior instanceof Warrior)   // true
console.log(randomWarrior instanceof Character) // true

// Polymorphic function operating on shared interface
function executeBattle(chars) {
  return chars.map(c => c.attack())
}
console.log(executeBattle([new Warrior('A'), new Character('B')]))
// Output: ["A swings a mighty sword!", "B attacks!"]

Summary

  • Every JavaScript object links to another object via its internal [[Prototype]] slot, forming the prototype chain.
  • Class inheritance using extends creates two links: one from instance to child prototype, and one from child prototype to parent prototype.
  • The super keyword explicitly accesses parent class constructors and methods, while static members inherit through the constructor's own prototype chain.
  • instanceof traverses the entire prototype chain to determine membership, returning true for any ancestor constructor in the hierarchy.
  • This delegation model enables memory-efficient method sharing and polymorphic design patterns, as demonstrated in tests/object-oriented/inheritance-polymorphism/inheritance-polymorphism.test.js.

Frequently Asked Questions

What is the difference between __proto__ and prototype?

__proto__ is an accessor property that exposes the internal [[Prototype]] of an instance object, while prototype is a regular property on constructor functions that becomes the [[Prototype]] of instances created with that constructor. In modern JavaScript, use Object.getPrototypeOf() and Object.setPrototypeOf() instead of __proto__ for better performance and standards compliance.

How does the instanceof operator actually work?

instanceof checks if the constructor's prototype property exists anywhere in the object's prototype chain. Specifically, obj instanceof Constructor evaluates whether Constructor.prototype is reachable from obj following the [[Prototype]] links upward until null. This is why a Warrior instance returns true for both Warrior and Character checks.

Can you modify an object's prototype after it is created?

Yes, though it is generally discouraged for performance reasons. You can change an object's prototype using Object.setPrototypeOf(obj, newProto) or by setting __proto__, but doing so invalidates engine optimizations. It is better to set the prototype at creation time using Object.create(newProto) or class extends declarations.

Why does static inheritance work differently than instance inheritance?

Static members belong to the constructor function itself, not the prototype object. When class B extends A is parsed, JavaScript internally performs Object.setPrototypeOf(B, A), linking the constructor functions in a chain parallel to the instance prototype chain. This allows B to access static methods defined on A, such as B.createRandom() delegating to A.createRandom().

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →