Topics

Jump to a topic.

Search questions

Find a question by title, tag, or summary.

What is a realm, and why does instanceof Array fail across iframes?

Questions in this chapter (9)
  • Draft
  • hard
  • Askedrarely
  • Verified October 2026

Answer

A realm (a Realm Record in the spec) bundles a set of intrinsic objects, a global object and Global Environment Record, and the code loaded into them. Intrinsics are the built-ins the engine creates for you: %Array%, %Array.prototype%, %Object%, %Error%, and so on. CreateIntrinsics builds a brand-new set for each realm.

Where realms come from:

  • A same-origin <iframe> (or a popup) gets its own realm, so iframe.contentWindow.Array is a different function from your Array.
  • Node's vm contexts create realms as well.
  • Workers and the main thread have separate realms, but they can't share objects directly anyway.

Why instanceof breaks: x instanceof Array checks whether Array.prototype is in x's prototype chain. An array made in another realm inherits from that realm's Array.prototype, which is a different object. So foreignArray instanceof Array is false, even though it is a real array. The same applies to Object, Function, Error, Date, Map, and the rest.

Robust checks that don't depend on identity:

  • Array.isArray(x) looks at the object's exotic internal kind (IsArray), so it works across realms.
  • Error.isError(x) does the same for errors (a newer API; check support).
  • Object.prototype.toString.call(x) reads Symbol.toStringTag, which is mostly reliable but spoofable.
  • Duck typing (typeof x.then === 'function') for promises and iterables.

Some things are shared across realms. Well-known symbols (Symbol.iterator) and registered symbols (Symbol.for('k')) are the same in every realm, and primitives have no realm at all.

Example

import vm from 'node:vm';

// A vm context is a separate realm with its own Array, Object, Error, ...
const foreign = vm.runInNewContext('[1, 2]');

console.log(foreign instanceof Array); // → false (different Array.prototype)
console.log(Array.isArray(foreign)); // → true (checks the internal kind)
console.log(Object.prototype.toString.call(foreign)); // → [object Array]
console.log(foreign.constructor === Array); // → false
console.log(vm.runInNewContext('Symbol.iterator') === Symbol.iterator); // → true (shared)

Pitfalls

  • Using instanceof for built-in type checks on values that might come from a frame, a vm context, or a library loaded in another realm. Use Array.isArray and Error.isError, or duck typing.
  • Assuming a thrown error is instanceof Error. An error thrown from code in another realm isn't, though its name and message still work.
  • Believing realm boundaries are security boundaries. A vm context shares the process and its objects can reach back into yours; Node's docs say not to use it as a sandbox for untrusted code.
  • Mixing realms in class extends. A class extending a foreign Array creates objects whose prototype chain contains only the foreign Array.prototype.

Follow-ups

Why does Array.isArray work across realms?
IsArray tests whether the object is an Array exotic object, an internal property that doesn't depend on which prototype it has. For a Proxy it checks the target.
Does [].concat(foreignArray) flatten it?
Yes. concat decides whether to spread an argument using IsArray, not instanceof, so foreign arrays are spread like local ones.
In which realm do values returned by built-ins live?
In the realm of the function that creates them, not the caller's. A foreign map returns a foreign array. structuredClone returned an object of the current realm in Node 24.

Exercises

Which check passes?

import vm from 'node:vm';

const foreignError = vm.runInNewContext('new RangeError("boom")');
console.log(
  foreignError instanceof Error,
  foreignError.name,
  Error.isError(foreignError),
);

Catching across realms

import vm from 'node:vm';

try {
  vm.runInNewContext('null.x');
} catch (error) {
  console.log(error instanceof TypeError, error.name);
}