Right, let’s talk about instanceof. This is one of those operators that feels almost magical when you first see it, but its magic is firmly rooted in the dusty, well-trodden ground of JavaScript’s prototype chain. It’s your go-to tool when you need to ask an object, “Hey, who built you?”

In its simplest form, instanceof checks if an object is an instance of a specific class (or a constructor function, for you old-schoolers). It does this by walking up the object’s prototype chain to see if it can find the prototype property of the constructor you’re checking against. If it finds it, you get a true. Simple, right? Well, mostly.

How It Works Under the Hood

Don’t just take my word for it; let’s see the mechanic in action. When you write myValue instanceof MyClass, the JavaScript engine is essentially doing this:

function isInstanceOf(value, constructor) {
  let proto = Object.getPrototypeOf(value);
  while (proto !== null) {
    if (proto === constructor.prototype) {
      return true;
    }
    proto = Object.getPrototypeOf(proto);
  }
  return false;
}

It’s a recursive prototype walk. This is crucial to understand because it explains the behavior—and the pitfalls.

class Vehicle {
  drive() { console.log("Vroom vroom"); }
}

class Car extends Vehicle {
  honk() { console.log("Beep beep!"); }
}

const myCar = new Car();

// Both of these are true because the Car prototype
// inherits from the Vehicle prototype.
console.log(myCar instanceof Car);     // true
console.log(myCar instanceof Vehicle); // true
console.log(myCar instanceof Object);  // true (sigh, always this)

// And this is where TypeScript narrows the type.
if (myCar instanceof Vehicle) {
  myCar.drive(); // TypeScript now knows myCar is a Vehicle
  // myCar.honk(); // <-- Error! Property 'honk' does not exist on type 'Vehicle'.
}

See? TypeScript uses this runtime check to narrow the type within that if block. It’s not guessing; it’s using the same logic JavaScript does. If instanceof returns true, the type must be compatible with that class.

The Big, Glaring Pitfall: Cross-Realm Values

Here’s the part where the designers’ choice to tie this solely to the prototype chain becomes a genuine headache. instanceof fails spectacularly with values from different JavaScript execution contexts. Think iframes, node:vm modules, or different Node.js vm contexts. Each realm has its own global object, and therefore its own set of constructor functions.

// Imagine 'someValue' comes from an iframe
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = iframe.contentWindow.Array;

const myArray = [1, 2, 3];
const theirArray = new iframeArray(1, 2, 3);

console.log(myArray instanceof Array);          // true (obviously)
console.log(theirArray instanceof Array);       // false (wait, what?)
console.log(theirArray instanceof iframeArray); // true (but useless to you)

// Even though it quacks exactly like a duck:
console.log(Array.isArray(theirArray)); // true (the correct way to check)

This is absurd, but it’s the reality we live in. For built-ins like Array, Date, or Promise, you should almost always prefer Array.isArray(), typeof, or other checks over instanceof to avoid this cross-realm insanity.

Best Practices and When to Actually Use It

So, given that pitfall, when do you reach for instanceof? The answer is simple: for your own classes. You have complete control over them, and you’re unlikely to be shuttling your own class instances across iframe boundaries in a typical app.

It’s perfect for narrowing in polymorphic code, like event handlers or when dealing with data from a library that returns a base class or an interface.

class ApiSuccess {
  constructor(public data: any) {}
}
class ApiError {
  constructor(public message: string) {}
}

function handleApiResponse(response: ApiSuccess | ApiError) {
  if (response instanceof ApiSuccess) {
    // Type is narrowed to ApiSuccess
    console.log(`Data received: ${response.data}`);
  } else {
    // Type is narrowed to ApiError
    console.error(`Failed: ${response.message}`);
  }
}

This is clean, self-documenting, and type-safe. It’s what instanceof was made for. Just remember its limitations with built-in types and never, ever use it for something as simple as checking if a value is an array. You’re better than that. Use Array.isArray and let TypeScript do the rest.