skip to content

How does the JavaScript `instanceof` operator decide its answer, and what exactly does it compare?

level: middleimportance: must knowfreq 78%

answer

  1. not a check on constructor
  2. identity comparison along a chain
  3. the right-hand side is read live
  4. primitives answer false, never throw
  5. bound functions defer to their target

basics

~20 s

x instanceof F reads the current value of F.prototype and walks the prototype chain of x, returning true if that exact object appears anywhere in the chain. It compares object identity along a chain, not names, not x.constructor.

solid answer

~40 s

`x instanceof F` is a two-step operation. First it looks for a `Symbol.hasInstance` method on `F`; every ordinary function inherits one from `Function.prototype`, and that default implements the classic behaviour. The default reads `F.prototype` at the moment of evaluation, then walks `x`'s internal prototype chain — the links you would follow with repeated `Object.getPrototypeOf` — and returns `true` as soon as one link is the very same object as `F.prototype`. Three consequences follow. If `x` is a primitive it returns `false` immediately, never throws. If `F` is not callable, or `F.prototype` is not an object, it throws a `TypeError`. And because `F.prototype` is read live, reassigning it after objects were built makes those objects stop being instances. For a bound function it defers to the original target function.

code

javascript · 16 lines
javascript
function A() {}
const a = new A();

console.log(a instanceof A);            // true
console.log('abc' instanceof String);   // false — primitives are never instances

const BoundA = A.bind(null);
console.log(BoundA.prototype);          // undefined
console.log(a instanceof BoundA);       // true — checked against A.prototype

A.prototype = {};                       // fresh prototype object
console.log(a instanceof A);            // false — a still links to the old one
console.log(new A() instanceof A);      // true — new objects link to the new one

const fake = Object.create(A.prototype);
console.log(fake instanceof A);         // true — never went through new A()

go deeper

for a junior

Know the syntax and the everyday meaning: x instanceof Dog is true for objects that inherit from Dog.prototype, including subclass instances, and false for primitives.

for a middle

Walk the algorithm out loud: read F.prototype now, follow x's prototype chain, compare by identity, stop at null. Name the TypeError cases on the right-hand side.

for a senior

Show where the live read bites in real code — a library that reassigns a prototype, or objects that pass the check without ever being constructed — and pick checks that do not depend on mutable state.

for a principal

Argue when structural chain checks belong in an API contract at all, versus explicit capability tags or discriminated data, given that any consumer can synthesise an object that passes.

## The operator is a chain search, not a label check The most common wrong mental model is that `instanceof` asks "was this object made by this function?" It does not. It asks a purely structural question: *is the object currently referenced by `F.prototype` somewhere in `x`'s prototype chain?* Both halves of that sentence matter — it reads `F.prototype` now, and it compares by object identity, not by name or shape. ```js function A() {} const a = new A(); let p = Object.getPrototypeOf(a); while (p !== null) { if (p === A.prototype) console.log('found it'); p = Object.getPrototypeOf(p); } ``` That loop is essentially the whole operator. ## The spec path Evaluating `x instanceof F` runs roughly these steps: 1. If `F` is not an object, throw a `TypeError`. 2. Look up `F[Symbol.hasInstance]`. If it is not `undefined` or `null`, call it with `x` and coerce the result to a boolean. Ordinary functions inherit `Function.prototype[Symbol.hasInstance]`, so this branch is almost always taken and simply performs step 3. 3. Otherwise (a callable without that method), run the ordinary algorithm: if `F` is not callable, throw a `TypeError`; if `F` is a bound function, redo the check against its bound target function; read `F.prototype`; if that is not an object, throw a `TypeError`; then walk `x`'s prototype chain comparing each link with `===` against `F.prototype`; return `false` at the end of the chain. Before any of that, if `x` is not an object the ordinary algorithm returns `false`. So `'abc' instanceof String` is `false`, and `42 instanceof Number` is `false`, without an error. A primitive string is not a `String` object, and no autoboxing happens for this operator. ## What throws and what does not The error cases are worth memorising because they surprise people: ```js 'abc' instanceof String; // false — primitives are simply not instances ({}) instanceof 42; // TypeError: right-hand side is not an object ({}) instanceof {}; // TypeError: right-hand side is not callable function F() {} F.prototype = 5; // legal for an ordinary function ({}) instanceof F; // TypeError: F.prototype is not an object ``` A `TypeError` from `instanceof` therefore always points at the right-hand side, never at the left. ## Live reads of F.prototype Because the operator reads `F.prototype` when it runs, the relationship between an object and a function is not frozen at construction: ```js function A() {} const a = new A(); a instanceof A; // true A.prototype = {}; // brand-new object a instanceof A; // false — a still links to the OLD prototype object const b = new A(); b instanceof A; // true — b links to the new one a instanceof A; // still false ``` That pair of objects, both produced by calling `new A()`, now disagree about whether they are instances of `A`. This is the crispest demonstration that the operator is about the current chain, not about provenance. The same mechanism runs the other way: since `Object.setPrototypeOf` can splice any object into any chain, an object never touched by `A` can be made to pass: ```js const fake = Object.create(A.prototype); fake instanceof A; // true — never went through new A() ``` ## Inheritance chains and built-ins Because the search walks the entire chain, `instanceof` is naturally polymorphic: ```js class Animal {} class Dog extends Animal {} const d = new Dog(); d instanceof Dog; // true d instanceof Animal; // true — Animal.prototype is further up the chain d instanceof Object; // true — Object.prototype terminates most chains ``` An object created with `Object.create(null)` has an empty chain, so `obj instanceof Object` is `false` — a useful reminder that "everything is an object" is a simplification. ## Bound functions `f.bind(...)` produces a function with no own `prototype` property. If `instanceof` naively read `Bound.prototype` it would find `undefined` and throw. Instead the algorithm unwraps the binding and checks against the target function's prototype: ```js function A() {} const a = new A(); const BoundA = A.bind(null); BoundA.prototype; // undefined a instanceof BoundA; // true — checked against A.prototype ``` ## Practical reading Use `instanceof` when you control both the objects and the constructor in the same program and the same realm — checking your own error subclasses in a `catch` block is the classic good use. Be aware of its two structural weaknesses: the answer depends on a mutable `F.prototype`, and identity comparison means two structurally identical functions from different sources never recognise each other's objects. Neither is a bug; both fall directly out of "walk the chain and compare by identity".

  • Can an object pass `instanceof F` without ever having been created by `F`?
    Yes. `Object.create(F.prototype)` produces an object whose chain contains `F.prototype`, so it passes, even though `F` never ran. `Object.setPrototypeOf` can do the same to an existing object. The operator inspects the current chain, so any way of putting `F.prototype` into that chain is enough.
  • Why does `instanceof` throw a `TypeError` in some cases but simply return `false` for a primitive on the left?
    The error cases are all about the right-hand side: it must be an object, it must be callable, and its `prototype` must itself be an object, or there is nothing coherent to compare against. A primitive on the left is a well-defined question with a well-defined answer — it has no prototype chain of its own to search, so the result is `false`.
  • How does `instanceof` behave for a class hierarchy several levels deep, and what does that cost?
    It returns `true` for every ancestor whose prototype is on the chain, because the search continues upward until it hits `null`. The cost is a walk proportional to chain depth, which is negligible in practice; the real cost is design-level, since a deep chain means a lot of code implicitly answers `true` to broad type questions.

instanceof is a question about ancestry, not about a birth certificate: it checks whether a particular ancestor appears in the object's family line right now, not who was present when the object was made.

saying these in an interview costs you the question

  • Saying instanceof compares obj.constructor to the function
  • Claiming instanceof throws when the left side is a primitive
  • Believing the relationship is fixed at construction time
  • Thinking instanceof compares function names or shapes
  • Assuming any value is legal on the right-hand side

context