intermediateCommon5 min read · Updated Jul 19, 2026

Implement instanceof from Scratch

Implement instanceof in JavaScript by walking the prototype chain, understand Symbol.hasInstance customization, and know the cross-realm case where instanceof lies.


The problem

Implement myInstanceof(value, Constructor) without using instanceof.

The mechanism is one loop: walk value's prototype chain and look for Constructor.prototype. The question is a five-minute check of prototypal inheritance fluency — and then the interview actually happens in the follow-ups: primitives, Symbol.hasInstance, and the cross-realm failure that motivated Array.isArray.

Implementation

function myInstanceof(value, Constructor) {
  if (typeof Constructor !== "function") {
    throw new TypeError("Right-hand side of 'instanceof' is not callable");
  }
 
  // Primitives are NEVER instances — no boxing happens here
  if (value === null || (typeof value !== "object" && typeof value !== "function")) {
    return false;
  }
 
  const target = Constructor.prototype;
  let proto = Object.getPrototypeOf(value);
 
  while (proto !== null) {
    if (proto === target) return true;   // found the link
    proto = Object.getPrototypeOf(proto); // climb one level
  }
  return false; // chain ended at null — not an instance
}

Verified behavior

class Animal {}
class Dog extends Animal {}
const rex = new Dog();
 
myInstanceof(rex, Dog);      // true  — first hop
myInstanceof(rex, Animal);   // true  — second hop: the chain WALK matters
myInstanceof(rex, Object);   // true  — everything plain ends at Object.prototype
myInstanceof([], Array);     // true
myInstanceof([], Object);    // true  — arrays inherit from Object too
 
myInstanceof("hi", String);            // false — primitives never match…
myInstanceof(new String("hi"), String); // true  — …but boxed objects do
myInstanceof(null, Object);            // false — null is nobody's instance
myInstanceof(Object.create(null), Object); // false — null-proto chain, zero hops

The "hi" vs new String("hi") pair is the classic probe: instanceof does no coercion — a string primitive has no prototype chain to walk, even though property access on it temporarily boxes. If you can articulate that asymmetry (reads box, instanceof doesn't), the fundamentals box is checked.

Also worth narrating: Object.getPrototypeOf, not __proto__ — the accessor is legacy (absent on Object.create(null) objects, deprecated in spec annex), while the function form always works.

The modern half: Symbol.hasInstance

Since ES6, instanceof isn't hardwired to the chain walk — it first asks the right-hand side:

class Even {
  static [Symbol.hasInstance](value) {
    return typeof value === "number" && value % 2 === 0;
  }
}
 
4 instanceof Even;   // true (!) — no prototypes involved at all
"x" instanceof Even; // false

The real algorithm: if Constructor[Symbol.hasInstance] is a function, call it and coerce to boolean; only otherwise do the prototype walk (the "ordinary" path your implementation reproduces). A spec-faithful myInstanceof adds three lines at the top:

const custom = Constructor[Symbol.hasInstance];
if (typeof custom === "function") {
  return Boolean(custom.call(Constructor, value));
}

Where this is real: zod-style schema checks, pattern-matching libraries, and — the platform's own use — instanceof working sensibly for Proxy-wrapped classes. It also means instanceof is spoofable, which matters for the security-adjacent follow-up: never gate trust decisions on it. (Same lesson as Symbol.toStringTag spoofing type tags — the language's introspection is cooperative, not authoritative.)

Where instanceof lies: cross-realm objects

An array from an iframe, worker message, or vm context was built by a different Array constructor with a different Array.prototype:

frameArray instanceof Array;        // false ✗ — different realm's prototype
Array.isArray(frameArray);          // true  ✓ — checks an internal slot instead

Your chain walk is correct and still gives the "wrong" answer — the chain genuinely doesn't contain your Array.prototype. This single case is why Array.isArray exists, why structured data crossing boundaries should be checked structurally (duck typing / schema validation) rather than by constructor identity, and why instanceof Error checks break across realms too (a real bug class in error-handling middleware). Delivering this unprompted is the difference between implementing the operator and understanding it.

Edge cases interviewers probe

  • Deep chainsrex instanceof Animal requires the loop, not a single comparison; interviewers test with a two-level class hierarchy.
  • Function weirdnessFunction instanceof Function is true (functions are objects whose chain includes Function.prototype); Object instanceof Function is also true. Fun chain-tracing warm-downs.
  • Mutated prototypes — reassigning Constructor.prototype after construction orphans old instances (old instanceof Cfalse); the operator checks current linkage, not construction history. This is the inverse view of what new wires up.
  • Non-callable RHSx instanceof {} throws TypeError; bound functions and proxies as RHS work (and route through Symbol.hasInstance / target's prototype).
  • Object.create(null) — zero-hop chain; false for everything, and any implementation using value.__proto__ instead of getPrototypeOf misses it silently.

Common mistakes

  • Recursion or a single equality check instead of the loop (fails inheritance).
  • Using __proto__.
  • Boxing primitives "helpfully" (myInstanceof("hi", String) returning true is wrong).
  • Not knowing Symbol.hasInstance exists — post-2015, the operator is dispatchable, and "implement instanceof" without it is half the algorithm.
  • Recommending instanceof for array/plain-object detection in code review — Array.isArray and structural checks are the production answers.

Follow-up questions

  • "Implement the new operator to go with it."the pairing: new builds the link, instanceof queries it.
  • "How would you check types across realms reliably?"Array.isArray for arrays; Object.prototype.toString.call tags for built-ins (the getType story); structural/schema validation for your own shapes.
  • "What does x instanceof BoundFn do?" — bound functions delegate to the target function's prototype ([[BoundTargetFunction]]) — which is precisely why the bind polyfill's prototype linking matters.
  • "Design a safe isPlainObject." — prototype is Object.prototype or null: const p = Object.getPrototypeOf(x); return p === Object.prototype || p === null — the utility every serializer needs, built from this question's parts.
  • "Why is instanceof slow-ish in hot paths?" — potential chain walk + hasInstance lookup per check; engines cache aggressively, but monomorphic tag checks or discriminant fields (kind: "user") beat it in hot code — the discriminated-union pattern TypeScript rewards anyway.

  • Object.assign

    intermediate

    Copy enumerable own properties across sources, with getter evaluation and null-target errors.

  • The new Operator

    intermediate

    Implement myNew(Constructor, ...args): prototype linking, this binding, and the return-object override rule.

  • Object.is

    beginner

    The equality that fixes === for NaN and -0: tiny implementation, classic trivia follow-ups.

  • Implement bind including partial application and the new-operator edge case most candidates miss.