Class checking: "instanceof"
instanceof answers one question: does this object come from a given class? And it answers it honestly across inheritance, so a Sparrow counts as a Bird too.
That makes it the tool of choice for polymorphic code — a function that inspects its argument and behaves differently depending on what it received. Feed it an array and it does one thing; feed it a plain object and it does another. instanceof is how the function tells them apart.
The instanceof operator
The syntax is a single binary operator:
obj instanceof Class
It evaluates to true when obj was produced by Class, or by any class that inherits from Class. Otherwise false.
Start with the simplest possible case:
class Sparrow {}
let sparrow = new Sparrow();
// is it an object of Sparrow class?
alert( sparrow instanceof Sparrow ); // true
sparrow was made with new Sparrow(), so the check passes.
Classes are the modern spelling, but instanceof predates them. It works just as well with the old constructor-function style, because a class is a constructor function under the hood:
// a constructor function instead of a class
function Sparrow() {}
alert( new Sparrow() instanceof Sparrow ); // true
And it works with the built-in classes the engine ships:
let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true
Notice that arr reports true for both Array and Object. That is not a quirk — it is inheritance doing its job. Array sits on top of Object in the prototype chain, so every array is also an object. instanceof sees the whole chain, not just the nearest class.
Try it for yourself. Below, sparrow is built from a small Sparrow extends Bird hierarchy. Test it against each class and watch instanceof say true for every ancestor on the chain — and false for a class that isn’t on it.
How the check actually runs
Most of the time instanceof just walks the prototype chain. But there’s an escape hatch that runs first: a static method named Symbol.hasInstance. Knowing both branches keeps you from being surprised.
Here is the algorithm for obj instanceof Class, in order:
Step 1 — is there a Symbol.hasInstance? If Class defines a static method under the well-known symbol Symbol.hasInstance, the engine hands off the whole decision to it: it calls Class[Symbol.hasInstance](obj) and uses the boolean it returns. Nothing else happens. This is your hook for completely custom membership logic.
// treat anything with a truthy canFly property as a Bird,
// regardless of how it was actually constructed
class Bird {
static [Symbol.hasInstance](obj) {
if (obj.canFly) return true;
}
}
let obj = { canFly: true };
alert(obj instanceof Bird); // true → Bird[Symbol.hasInstance](obj) ran
Here obj was never created with new Bird(). It’s a plain object literal. But because Bird defines Symbol.hasInstance and that method looks at canFly instead of prototypes, the check reports true.
The demo below wires up exactly that Symbol.hasInstance. Toggle the canFly flag on a plain object literal — no new anywhere — and watch instanceof Bird flip, driven entirely by the custom method.
Step 2 — the standard prototype walk. The vast majority of classes have no Symbol.hasInstance. In that case JavaScript falls back to comparing prototypes: it checks whether Class.prototype shows up anywhere in obj’s prototype chain.
Conceptually it compares link by link:
obj.__proto__ === Class.prototype?
obj.__proto__.__proto__ === Class.prototype?
obj.__proto__.__proto__.__proto__ === Class.prototype?
// ...climbing until a match (→ true) or the chain ends (→ false)
For the plain sparrow from earlier, sparrow.__proto__ is Sparrow.prototype, so the very first comparison matches and the answer comes back immediately.
With inheritance the match happens one step higher:
class Bird {}
class Sparrow extends Bird {}
let sparrow = new Sparrow();
alert(sparrow instanceof Bird); // true
// sparrow.__proto__ === Bird.prototype ? no
// sparrow.__proto__.__proto__ === Bird.prototype ? yes → true
There’s a related built-in that does the same walk directly: objA.isPrototypeOf(objB) returns true when objA appears somewhere in objB’s chain. So obj instanceof Class is essentially another way of writing Class.prototype.isPrototypeOf(obj).
The gotcha: the constructor never participates
Read step 2 again and something jumps out. The comparison only ever touches Class.prototype and the chain of prototypes. The Class function itself — the constructor you called with new — is never inspected.
That has a surprising consequence. Swap out .prototype after an object already exists, and that object stops being an “instance,” even though nothing about the object changed:
function Sparrow() {}
let sparrow = new Sparrow();
// replace the prototype object entirely
Sparrow.prototype = {};
// ...not a sparrow any more!
alert( sparrow instanceof Sparrow ); // false
sparrow still links to the original prototype object that existed at new time. But Sparrow.prototype now points at a fresh empty object. Since instanceof compares sparrow’s chain against the current Sparrow.prototype, and they no longer coincide, the check fails.
Bonus: Object.prototype.toString as a type detector
You already know that a plain object stringifies to the unhelpful [object Object]:
let obj = {};
alert(obj); // [object Object]
alert(obj.toString()); // the same
That text comes from Object.prototype.toString. It looks useless, but there’s more inside it than the default output suggests. Borrowed and applied to other values, it becomes a compact type detector — an extended typeof and, for built-ins, an alternative to instanceof.
Per the language specification, this built-in toString can be pulled off Object.prototype and run against any value, and its output depends on what that value is:
- a number →
[object Number] - a boolean →
[object Boolean] null→[object Null]undefined→[object Undefined]- an array →
[object Array] - …and so on, and it’s customizable.
The trick is to invoke it with an explicit this, using call (covered in Decorators and forwarding, call/apply):
// grab the method once for convenience
let objectToString = Object.prototype.toString;
// what type is this?
let arr = [];
alert( objectToString.call(arr) ); // [object Array]
call(arr) runs objectToString with this = arr. Internally the algorithm inspects this, decides on a tag, and wraps it as [object <Tag>]. More values through the same door:
let s = Object.prototype.toString;
alert( s.call(123) ); // [object Number]
alert( s.call(null) ); // [object Null]
alert( s.call(alert) ); // [object Function]
Run the detector yourself. Each button feeds a different value through the same {}.toString.call(value) — a number, an array, null, a function, a date — and the one method reports a distinct tag for each.
Symbol.toStringTag
The tag inside [object ...] isn’t fixed. You can set it on any object with the well-known property Symbol.toStringTag:
let member = {
[Symbol.toStringTag]: "Member"
};
alert( {}.toString.call(member) ); // [object Member]
Many host objects already define this property, which is why the detector reports meaningful names for browser-provided things:
// toStringTag for environment-specific objects and classes:
alert( window[Symbol.toStringTag] ); // Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest
alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]
The output is exactly whatever Symbol.toStringTag holds, wrapped in [object ...]. The result: a “typeof on steroids” that names primitives, built-in objects, and any object you’ve tagged yourself.
When you want the type as a string for a built-in — rather than a yes/no membership test — {}.toString.call(value) is the reach-for tool, in place of instanceof.
Summary
Three type-checking tools, each with a different reach:
| works for | returns | |
|---|---|---|
typeof |
primitives | string |
{}.toString |
primitives, built-in objects, objects with Symbol.toStringTag |
string |
instanceof |
objects | true / false |
{}.toString.call(value) is the technically broader detector — a more advanced typeof that returns a readable tag.
instanceof earns its keep when you’re working inside a class hierarchy and need a yes/no answer that respects inheritance. It walks the prototype chain, so a child instance correctly tests true against every ancestor class — unless you’ve reassigned a .prototype out from under it, or wired up a custom Symbol.hasInstance to decide for yourself.