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:
- JavaScript checks if
B[Symbol.hasInstance]exists and is callable. - If it is callable, it invokes
B[Symbol.hasInstance](a)and converts the result to a boolean. - If
Symbol.hasInstanceis not defined or isundefined/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); // falseKey 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); // false2. 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