skip to content

An array handed to your code from a same-origin `<iframe>` fails `value instanceof Array`. Explain why, and how you would make the check reliable.

level: seniorimportance: should knowfreq 40%

answer

  1. one environment, one set of built-ins
  2. identity fails where structure matches
  3. the object is not a copy
  4. a slot, not a chain
  5. two package copies do the same thing

basics

~20 s

Each realm has its own set of built-ins, so the iframe's Array is a different function object with a different Array.prototype. The value's chain holds the iframe's prototype, so the identity comparison fails. Use Array.isArray, which is realm-independent.

solid answer

~40 s

An iframe, a worker, or a Node `vm` context is a separate realm with its own global object and its own complete set of intrinsics. The iframe's `Array` and your page's `Array` are two distinct function objects with two distinct `Array.prototype` objects. Since `instanceof` compares by identity along the prototype chain, an array built inside the iframe carries the iframe's `Array.prototype` and never matches yours — so the check returns `false` even though the value is a genuine array. The reliable fix for arrays is `Array.isArray(value)`, which inspects an internal slot rather than the chain and therefore works across realms; `Object.prototype.toString.call(value)` reads the same kind of internal information. The same failure appears without realms at all when a bundle ships two copies of a library, giving you two unrelated class objects.

code

javascript · 12 lines
javascript
const frame = document.createElement('iframe');
document.body.appendChild(frame);

const foreign = new frame.contentWindow.Array(1, 2, 3);

console.log(foreign instanceof Array);        // false — different realm
console.log(foreign.constructor === Array);   // false
console.log(foreign.constructor.name);        // "Array" — same name, other identity

console.log(Array.isArray(foreign));          // true
console.log(Object.prototype.toString.call(foreign)); // "[object Array]"
console.log(foreign.length);                  // 3 — it is a real array

go deeper

for a junior

Know that Array.isArray is the correct way to test for an array, and that instanceof can answer false for values that came from another window or frame.

for a middle

Explain realms: each iframe or worker has its own intrinsics, so identity comparison against your Array.prototype fails even though the value is a genuine array.

for a senior

Diagnose it in production — recognise the duplicate-package variant with no realms involved, and replace fragile chain checks with markers or exported predicates at trust boundaries.

for a principal

Set the policy for cross-boundary type identity: which values may cross realms or package copies, what stable tag every shared type carries, and how dependency resolution is constrained so identity stays single.

## Realms, and why there is more than one Array A *realm* is a complete JavaScript execution environment: a global object plus a fresh set of intrinsic objects — `Object`, `Array`, `Function`, `Error`, `Promise` and their prototypes. Each same-origin `<iframe>`, each worker, and each Node `vm` context gets its own realm. Objects can cross freely between same-origin realms without being copied, so you can end up holding a live reference to an object whose prototype chain terminates in someone else's intrinsics. ```js const frame = document.createElement('iframe'); document.body.appendChild(frame); const foreign = new frame.contentWindow.Array(1, 2, 3); foreign instanceof Array; // false Object.getPrototypeOf(foreign) === Array.prototype; // false Object.getPrototypeOf(foreign) === frame.contentWindow.Array.prototype; // true ``` Nothing is broken here. `instanceof` is doing exactly what it always does: it reads *your* `Array.prototype` and searches the value's chain for that specific object. The chain contains a structurally identical but distinct object, and identity comparison says no. ## The same failure without any iframe Realms are the textbook case, but the version that actually costs teams time has no realms in it. If a bundle or a `node_modules` tree contains two copies of the same library — say two versions of an error class, or a peer dependency installed twice — then two distinct class objects exist in one realm. A value produced by copy A fails `instanceof` against copy B: ```js // module built against copy A throw new ValidationError('bad'); // consumer that resolved copy B catch (e) { if (e instanceof ValidationError) { /* never runs */ } } ``` The symptom is identical: a value that obviously *is* the right thing fails a chain-identity test, and the error falls through to a generic handler. ## Diagnosing it Three checks separate this from an ordinary bug quickly: 1. `Object.prototype.toString.call(value)` still reports `"[object Array]"`, so the value really is an array. 2. `value.constructor === Array` is also `false`, and `value.constructor.name` is still `"Array"` — two functions with the same name and different identity is the signature of the problem. 3. `Object.getPrototypeOf(value) === Array.prototype` is `false` while the object behaves like an array in every other way. For the duplicate-package variant, resolving the module twice and comparing the exported class objects with `===` confirms it directly. ## Checks that survive it For arrays specifically, the language provides a purpose-built answer: ```js Array.isArray(foreign); // true, regardless of realm ``` `Array.isArray` does not consult the prototype chain at all; it asks whether the value is an array exotic object, which is a property of the value itself. That is why it is the correct check whenever a value might come from anywhere. `Object.prototype.toString.call(value)` gives similar cross-realm-safe answers for several built-in types, though `Symbol.toStringTag` lets user objects influence the result, so it is a hint rather than a guarantee. For **your own** types there is no built-in equivalent, so the durable patterns are: - **Duck typing on a stable marker.** Give the type an own or prototype property with a namespaced string or symbol key and test for that instead of the chain. `Symbol.for('myapp.ValidationError')` is registered in a cross-realm symbol registry, so the same key resolves to the same symbol in every realm — a genuinely realm-proof marker. - **A static predicate.** Export `ValidationError.isValidationError(x)` alongside the class, implemented with the marker check, so consumers never write the fragile test themselves. - **Fix the duplication.** For the two-copies case, the real remedy is dependency hygiene — deduplicate, or declare the shared library a peer dependency — because two copies also mean two module-level caches and two sets of any other module state. ## When instanceof is still the right tool None of this makes `instanceof` bad. Within one realm, with one copy of the code, checking your own error subclasses in a `catch` block is idiomatic and readable. The judgment being tested is knowing where the boundary is: the moment a value can arrive from an iframe, a worker, a deserialiser, or a differently-resolved copy of a package, a chain-identity check is answering a narrower question than the one you meant to ask, and you need a check based on something intrinsic to the value rather than on which prototype object it happens to point at.

  • Why does `Array.isArray` succeed where `instanceof Array` fails?
    `Array.isArray` does not look at the prototype chain. It asks whether the value is an array exotic object — a fact recorded in the value itself, not in what it inherits from. Realm boundaries change which prototype objects exist, but they cannot change what kind of object a value is, so the answer stays correct wherever the array was created.
  • What is the equivalent failure for `Error`, and what breaks because of it?
    An error thrown inside an iframe or worker fails `e instanceof Error` in the outer realm for the same reason. Error-handling code that branches on error subclasses then falls through to a generic handler, so a recoverable condition gets reported as an unknown failure. Checking a namespaced marker property, or a registered symbol, keeps working across the boundary.
  • How would you make your own class recognisable across realms?
    Attach a stable marker rather than relying on the chain: a property keyed with `Symbol.for('myapp.Thing')`, which resolves to the same symbol in every realm through the global symbol registry, or a namespaced string field. Export a static predicate such as `Thing.isThing(x)` that performs the check, so consumers have one correct way to ask.

saying these in an interview costs you the question

  • Claiming instanceof Array always works for real arrays
  • Assuming same-origin means the same intrinsic objects
  • Thinking the value was copied or degraded in transit
  • Believing typeof can distinguish arrays from objects
  • Assuming two package copies cannot break instanceof

context