skip to content

What does `Symbol.hasInstance` do to the `instanceof` operator, and why does `fn[Symbol.hasInstance] = handler` usually fail to take effect?

level: middleimportance: nice to knowfreq 22%

answer

  1. instanceof asks the right-hand side first
  2. every function already inherits one
  3. primitives can pass after all
  4. the inherited default is locked
  5. define, do not assign

basics

~10 s

Symbol.hasInstance is the hook instanceof calls first: if the right-hand operand has that method, its boolean result is the answer and no prototype chain is walked. Plain assignment fails because Function.prototype[Symbol.hasInstance] is non-writable.

solid answer

~40 s

Evaluating `x instanceof F` first looks up `F[Symbol.hasInstance]`. Every function inherits a default implementation from `Function.prototype`, and that default performs the familiar prototype-chain walk. Define your own — as a static method on a class, or with `Object.defineProperty` on a function — and `instanceof` calls it with the left operand and coerces whatever it returns to a boolean. That means `instanceof` can answer questions about primitives, about structural shape, or about anything else you choose. Plain assignment `F[Symbol.hasInstance] = fn` does not work because the inherited `Function.prototype[Symbol.hasInstance]` is non-writable and non-configurable, so an ordinary assignment cannot create the own property — it throws a `TypeError` in strict mode, including module code, and fails silently in sloppy mode. Use `Object.defineProperty` or `static [Symbol.hasInstance]()` in a class body.

code

javascript · 19 lines
javascript
class Even {
  static [Symbol.hasInstance](value) {
    return Number.isInteger(value) && value % 2 === 0;
  }
}

console.log(4 instanceof Even);    // true — a primitive passes
console.log(5 instanceof Even);    // false
console.log('4' instanceof Even);  // false

function Legacy() {}
Object.defineProperty(Legacy, Symbol.hasInstance, {
  value: (v) => typeof v === 'string'
});
console.log('hi' instanceof Legacy); // true

console.log(
  Object.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance).writable
); // false — which is why plain assignment cannot work

go deeper

for a junior

Know that instanceof normally checks the prototype chain, and that a well-known symbol exists which lets a type customise the answer. Recognising the name is enough at this level.

for a middle

Explain the lookup order — Symbol.hasInstance first, chain walk only as the default — and show why assignment fails while Object.defineProperty or a static class method works.

for a senior

Judge when redefining a core operator earns its keep, such as capability checks that survive realm and package-duplication boundaries, and what it costs readers who trust instanceof at a glance.

for a principal

Own the convention: whether the codebase permits overriding built-in operator hooks at all, and how a structural-identity contract is documented so consumers are not surprised by an expression that looks ordinary.

## The hook and where it sits `Symbol.hasInstance` is one of the well-known symbols added in ES2015. It names the method that the `instanceof` operator consults before doing anything else: 1. Evaluate `x instanceof F`; if `F` is not an object, throw a `TypeError`. 2. Get `F[Symbol.hasInstance]`. 3. If it is neither `undefined` nor `null`, call it with `x` as the argument and return the result coerced with `ToBoolean`. 4. Otherwise fall back to the ordinary algorithm, which requires `F` to be callable and walks `x`'s prototype chain against `F.prototype`. The subtlety is that step 3 is essentially always the branch taken. `Function.prototype[Symbol.hasInstance]` exists and holds the default implementation, so every function inherits it. Step 4 exists for the rare object that is callable without inheriting from `Function.prototype`. What that buys you is a documented extension point: the prototype-chain walk is the *default* meaning of `instanceof`, not its only meaning. ## Defining your own In a class, a static computed method is the clean form: ```js class Even { static [Symbol.hasInstance](value) { return Number.isInteger(value) && value % 2 === 0; } } 4 instanceof Even; // true 5 instanceof Even; // false '4' instanceof Even; // false ``` Note what just happened: `4` is a primitive, and the ordinary algorithm returns `false` for every primitive without exception. The hook bypasses that entirely, because the operator never reaches the chain walk. The return value is coerced, not required to be a boolean, so a truthy value counts as `true`. Returning a non-boolean is legal but unkind to readers. ## Why assignment silently does nothing The descriptor of the inherited default is the whole story: ```js Object.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance); // { value: [Function], writable: false, enumerable: false, configurable: false } ``` Plain assignment to a property that does not exist as an own property first consults the prototype chain. If it finds a non-writable data property there, the assignment is rejected: it throws a `TypeError` in strict mode and fails silently in sloppy mode. Module code and class bodies are always strict, so in modern code you usually get the error rather than the silence — but the silent version in a plain script is a genuinely confusing bug, because the code looks correct and `instanceof` simply keeps behaving the old way. Both working forms define an own property rather than assigning: ```js function Legacy() {} Object.defineProperty(Legacy, Symbol.hasInstance, { value: (v) => typeof v === 'string' }); 'hi' instanceof Legacy; // true ``` A `static [Symbol.hasInstance]()` in a class body works because class element definition uses the define semantics, not assignment. ## Where it is genuinely used The honest answer is: rarely, and deliberately. - **Abstract-class guards.** A base declares `static [Symbol.hasInstance](x)` that checks for required methods, so `x instanceof Serializable` becomes a capability question rather than a lineage question. That is the structural-typing use, and it survives the duplicate-copy and cross-realm problems that plague chain identity, because it tests the value rather than its ancestry. - **Branded value types.** A type whose values are primitives or plain objects can still answer `instanceof` sensibly. - **Testing and matcher libraries.** A matcher object can accept `instanceof` as a readable spelling of "matches this predicate". Against that, the cost is real: `instanceof` is the one operator readers assume they understand without looking anything up. Overriding it makes a familiar expression mean whatever an arbitrary function decides, and the redefinition is invisible at the call site. A misbehaving implementation — one that throws, or that has side effects — turns every `instanceof` in the codebase into a hazard, since nothing about `x instanceof F` suggests a user function runs. ## What it does not change Two boundaries are worth stating explicitly. The hook affects only `instanceof`; it does not change what `new F()` produces, what the prototype chain contains, or what `Object.getPrototypeOf` returns. And it lives on the *right-hand operand*, so it is the type's author who opts in — a value cannot make itself pass someone else's check by defining the symbol on itself. In an interview, knowing this hook exists is a differentiator rather than a requirement. The strongest answer names it, shows the define-versus-assign trap, gives one honest use case, and then says plainly that redefining a core operator is a decision you justify rather than a technique you reach for.

  • If every function inherits `Function.prototype[Symbol.hasInstance]`, when does `instanceof` ever run the ordinary chain-walk branch directly?
    Only when the right-hand operand does not inherit that default — for example a callable object created with `Object.create(null)` as its prototype, or one whose prototype chain was rewired away from `Function.prototype`. In everyday code the hook branch always runs, and the default implementation performs the chain walk, so the observable behaviour is the same.
  • Can `Symbol.hasInstance` make `instanceof` return `true` for a primitive?
    Yes, and that is the clearest proof the hook precedes the ordinary algorithm. The default returns `false` for any non-object left operand, but a custom implementation receives the primitive as its argument and can return anything. `4 instanceof Even` returning `true` is legal and well-defined.
  • What is the argument against using this hook in a shared codebase?
    `instanceof` is the operator readers trust themselves to understand at a glance, and the override is invisible at the call site. Anyone reading `x instanceof F` now has to find `F` to learn what the expression means, and a hook that throws or has side effects makes an innocuous-looking expression dangerous. Use a named predicate unless the readability win is real.

saying these in an interview costs you the question

  • Saying instanceof always walks the prototype chain
  • Enabling the hook with a plain assignment on a function
  • Claiming it changes what new produces
  • Believing the value can define it on itself
  • Assuming only classes can define Symbol.hasInstance

context