How JavaScript instanceof Works Under the Hood

The instanceof operator in JavaScript tests whether an object has a constructor’s prototype property anywhere in its prototype chain. Rather than checking the literal class or instance type of an object directly, it performs a search along the internal prototype chain or delegates the check to a custom method defined via Symbol.hasInstance. This article explains the underlying mechanism of instanceof, how prototype chain traversal works, the role of Symbol.hasInstance, and common pitfalls like multi-realm execution contexts.

The Core Mechanism: Prototype Chain Traversal

When you execute object instanceof Constructor, JavaScript does not check the constructor function that instantiated the object. Instead, it inspects the internal [[Prototype]] link (accessible via Object.getPrototypeOf(object)) and traverses the chain upward until it either finds Constructor.prototype or reaches the end of the chain (null).

Under the hood, the standard algorithm works conceptually like this:

function customInstanceOf(object, constructor) {
  // Primitives always return false
  if (object === null || (typeof object !== "object" && typeof object !== "function")) {
    return false;
  }

  // Ensure the constructor has a prototype object
  let targetPrototype = constructor.prototype;
  if (Object(targetPrototype) !== targetPrototype) {
    throw new TypeError("Right-hand side 'prototype' is not an object");
  }

  // Traverse the prototype chain
  let currentPrototype = Object.getPrototypeOf(object);
  while (currentPrototype !== null) {
    if (currentPrototype === targetPrototype) {
      return true;
    }
    currentPrototype = Object.getPrototypeOf(currentPrototype);
  }

  return false;
}

The Role of Symbol.hasInstance

Since ECMAScript 2015 (ES6), the instanceof operator does not always default directly to prototype traversal. It first checks if the constructor defines a method identified by the well-known symbol Symbol.hasInstance.

When evaluating a instanceof B:

  1. JavaScript checks if B[Symbol.hasInstance] exists and is callable.
  2. If it is callable, it invokes B[Symbol.hasInstance](a) and converts the result to a boolean.
  3. If Symbol.hasInstance is not defined or is undefined/null, it falls back to the standard prototype chain lookup algorithm.

This allows developers to customize instance verification dynamically:

class EvenNumber {
  static [Symbol.hasInstance](instance) {
    return typeof instance === "number" && instance % 2 === 0;
  }
}

console.log(4 instanceof EvenNumber); // true
console.log(5 instanceof EvenNumber); // false

Key Characteristics and Edge Cases

1. Prototype Mutation Affects Results

Because instanceof reads the live prototype chain and the current value of Constructor.prototype, mutating either property alters the result of subsequent checks:

function Animal() {}
const dog = new Animal();

console.log(dog instanceof Animal); // true

// Overwrite the prototype reference
Animal.prototype = {};

console.log(dog instanceof Animal); // false

2. Cross-Realm and iFrame Limitations

A known limitation of instanceof occurs in environments with multiple execution contexts (such as <iframe> elements in browsers or vm modules in Node.js).

Each realm has its own distinct global scope and built-in prototypes. An array created inside an iframe inherits from that iframe’s Array.prototype. If you test that array in the parent window using parentArray instanceof Array, it returns false because the iframe’s array prototype is not identical to the parent window’s Array.prototype. For this reason, utility methods like Array.isArray() are preferred over instanceof Array.

3. Primitive Types

Primitive values (such as string literals, numbers, and booleans) evaluate to false when evaluated with instanceof, even though they have corresponding object wrappers:

const strPrimitive = "hello";
const strObject = new String("hello");

console.log(strPrimitive instanceof String); // false
console.log(strObject instanceof String);    // true