JavaScript (ES2026) Module 11 – Prototypes and classes
instanceof, duck typing and cross-realm checks
Why instanceof walks the prototype chain and fails for values from iframes or vm contexts, which checks work across realms, and where duck typing fits.
What you will learn
- Check types with instanceof, Array.isArray, Error.isError and duck typing
- Explain why instanceof fails for values made in another realm, such as an iframe or a vm context
- Choose checks that give the same answer across realms and across two copies of a library
- Use Symbol.hasInstance to change what instanceof answers, and recognise when not to
Before you start
What you’ll learn · Before you start
What you will learn
- Check types with instanceof, Array.isArray, Error.isError and duck typing
- Explain why instanceof fails for values made in another realm, such as an iframe or a vm context
- Choose checks that give the same answer across realms and across two copies of a library
- Use Symbol.hasInstance to change what instanceof answers, and recognise when not to
Before you start
On this page
JavaScript has several ways to ask “what is this value?”, and each answers a different question. instanceof walks
the value’s prototype chain, looking for a constructor’s prototype object. That is quick and usually right, but it
fails for values made in another realm: every window, iframe, worker and Node.js vm context has its own set of
built-ins, so an array made in an iframe is not instanceof the parent page’s Array. Array.isArray and, since
ECMAScript 2026, Error.isError look at the object itself and work across realms. Duck typing checks what a
value can do instead of what made it, such as having a callable then or Symbol.iterator; it is how await
recognises promises and for…of recognises iterables. And Symbol.hasInstance lets a class decide what instanceof
answers. This lesson shows what each check really asks, reproduces the cross-realm failure in a second realm, and
chooses checks that survive iframes and two copies of the same library.
What instanceof really checks
walkChain below does what instanceof does for an ordinary class, and the two agree on every case:
// What `value instanceof C` does for an ordinary class: walk value's prototype chain, looking for C.prototype.
function walkChain(value, C) {
if (typeof value !== 'object' && typeof value !== 'function') return false; // primitives have no chain
if (value === null) return false;
for (let proto = Object.getPrototypeOf(value); proto !== null; proto = Object.getPrototypeOf(proto)) {
if (proto === C.prototype) return true;
}
return false;
}
class Vehicle {}
class Bus extends Vehicle {}
const bus = new Bus();
const lookalike = Object.create(Bus.prototype); // never passed through a constructor
const dictionary = Object.create(null); // no prototype at all
const cases = [
['bus, Bus', bus, Bus],
['bus, Vehicle', bus, Vehicle],
['bus, Object', bus, Object],
['lookalike, Bus', lookalike, Bus],
['dictionary, Object', dictionary, Object],
["'MH12', String", 'MH12', String],
["new String('MH12'), String", new String('MH12'), String],
];
for (const [label, value, C] of cases) {
console.log(label.padEnd(28), String(value instanceof C).padEnd(6), walkChain(value, C));
}
// The answer follows the chain at the moment you ask, not how the object was made.
Object.setPrototypeOf(bus, null);
console.log('bus after setPrototypeOf(null):', bus instanceof Bus);
// The right side must be callable; a function without a prototype object is refused.
const arrow = () => {};
try {
({}) instanceof arrow;
} catch (err) {
console.log('instanceof an arrow function:', err.name);
} Output
bus, Bus true true
bus, Vehicle true true
bus, Object true true
lookalike, Bus true true
dictionary, Object false false
'MH12', String false false
new String('MH12'), String true true
bus after setPrototypeOf(null): false
instanceof an arrow function: TypeError
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node instanceof-walk.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
- It asks about the prototype chain, not about the constructor.
lookalikewas made withObject.create(Bus.prototype)and never passed through theBusconstructor, yet it isinstanceof Bus. The bus whose prototype was removed is not, althoughnew Bus()made it. - The left side must be an object. A primitive such as
'MH12'has no prototype chain of its own, so it is neverinstanceof String, whilenew String('MH12'), an object that wraps the text, is. - Objects without a prototype belong to nothing.
Object.create(null)is not eveninstanceof Object. - The right side must be a function with a
prototypeobject, or have aSymbol.hasInstancemethod, which comes later in this lesson. An arrow function has noprototype, so({}) instanceof arrowthrows aTypeError. A bound function is looked through:instanceofuses the function it was bound from.
Realms: why instanceof fails across iframes
The standard calls one complete set of built-ins, with its global object and the code that runs with it, a
realm. Each realm has its own Object, Array, Error and Map, and their own prototype objects:
An array from another realm, checked two ways
Text description of the diagram
At the top, the prototype chain of an array called arr, made in realm B, such as an iframe, a worker or a vm context. The chain runs from arr to realm B's Array.prototype, then to realm B's Object.prototype, and ends at null.
Below it, two questions asked by code in realm A. The first, arr instanceof Array, asks whether realm A's Array.prototype appears on that chain. It never does, because the chain holds realm B's prototypes, so the answer is false.
The second, Array.isArray(arr), asks whether arr itself is an array object, whichever realm made it. It is, so the answer is true.
In a browser, every window, iframe and worker is a realm. An iframe on the same origin can hand objects to the page
that contains it, and those objects keep their own realm’s prototypes. Node.js creates realms with its node:vm
module, which this example uses to make values in a second realm. Node.js documents that node:vm is not a security
mechanism; here it only produces values from another realm:
// A second realm in Node.js: node:vm runs code with its own global object and its own built-ins.
// (node:vm is not a security boundary; it is used here only to make values that come from another realm.)
import vm from 'node:vm';
const other = vm.createContext({});
const make = (source) => vm.runInContext(source, other);
const values = {
'array [1, 2, 3]': make('[1, 2, 3]'),
'new TypeError()': make('new TypeError("from the other realm")'),
'new Date(0)': make('new Date(0)'),
'new Map()': make('new Map([["Pune", 411001]])'),
'plain { city }': make('({ city: "Pune" })'),
};
const checks = {
'instanceof': (v) => v instanceof Array || v instanceof Error || v instanceof Date || v instanceof Map || Object.getPrototypeOf(v) === Object.prototype,
'isArray/isError': (v) => Array.isArray(v) || Error.isError(v),
'toString tag': (v) => Object.prototype.toString.call(v),
};
console.log('value'.padEnd(18), 'instanceof isArray/isError toString tag');
for (const [name, v] of Object.entries(values)) {
console.log(name.padEnd(18), String(checks.instanceof(v)).padEnd(11), String(checks['isArray/isError'](v)).padEnd(16), checks['toString tag'](v));
}
// Built-in methods check internal slots, which do not depend on the realm.
console.log('getTime on the other Date:', Date.prototype.getTime.call(values['new Date(0)']));
console.log('has on the other Map:', Map.prototype.has.call(values['new Map()'], 'Pune'));
// Well-known symbols and the Symbol.for registry are shared by every realm, so duck typing still works.
console.log('Symbol.iterator shared:', make('Symbol.iterator') === Symbol.iterator, [...values['array [1, 2, 3]']]);
console.log('Symbol.for shared:', make('Symbol.for("app.order")') === Symbol.for('app.order'));
// A plain object from the other realm has that realm's Object.prototype, whose own prototype is null.
const plain = values['plain { city }'];
console.log('prototype is our Object.prototype:', Object.getPrototypeOf(plain) === Object.prototype);
console.log('prototype of its prototype is null:', Object.getPrototypeOf(Object.getPrototypeOf(plain)) === null);
// structuredClone makes copies in this realm, so instanceof works on them.
console.log('after structuredClone:', structuredClone(values['array [1, 2, 3]']) instanceof Array); Output
value instanceof isArray/isError toString tag
array [1, 2, 3] false true [object Array]
new TypeError() false true [object Error]
new Date(0) false false [object Date]
new Map() false false [object Map]
plain { city } false false [object Object]
getTime on the other Date: 0
has on the other Map: true
Symbol.iterator shared: true [ 1, 2, 3 ]
Symbol.for shared: true
prototype is our Object.prototype: false
prototype of its prototype is null: true
after structuredClone: true
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node realm-check.mjs
instanceoffails for all five, and so does comparing a prototype with this realm’sObject.prototype.Array.isArrayandError.isErrorsaytrue, because they ask whether the object itself is an array or an error, whichever realm made it.- The
Object.prototype.toStringtag still names the type, because it reads the same internal markers. Any object can fake a tag, though, as the next section shows. - Built-in methods check those markers too.
Date.prototype.getTime.call(otherDate)works for a date from any realm and throws aTypeErrorfor anything that is not a real date;Map.prototype.has.calldoes the same for maps. That makes them the realm-proof test for built-ins that have noisXfunction. - Some things are shared. The standard says that well-known symbols such as
Symbol.iteratorare shared by all realms, and so is the registry behindSymbol.for. That is why duck typing works across realms. - Copies belong to the realm that makes them.
structuredClone, andpostMessage, which uses the same algorithm, create new objects in the receiving realm, so values that arrive that way passinstanceofagain.
Ten values, five checks
Each check answers its own question, and a table of ten values shows which:
// Ten values, five checks: which question each check really answers.
const { proxy: revoked, revoke } = Proxy.revocable([], {});
revoke();
const values = {
'[]': [],
'new Uint8Array(2)': new Uint8Array(2),
"{ length: 0 }": { length: 0 },
'new Proxy([], {})': new Proxy([], {}),
'new Error()': new Error('boom'),
'new Proxy(error, {})': new Proxy(new Error('boom'), {}),
'fake error': { name: 'Error', message: 'boom', stack: '' },
'tagged as Array': { [Symbol.toStringTag]: 'Array' },
'null': null,
'revoked proxy': revoked,
};
const attempt = (fn) => {
try {
return String(fn());
} catch (err) {
return err.name;
}
};
console.log('value'.padEnd(22) + 'typeof'.padEnd(8) + 'isArray'.padEnd(10) + 'instanceof Array'.padEnd(18) + 'isError'.padEnd(9) + 'toString tag');
for (const [name, v] of Object.entries(values)) {
console.log(
name.padEnd(22) +
(typeof v).padEnd(8) +
attempt(() => Array.isArray(v)).padEnd(10) +
attempt(() => v instanceof Array).padEnd(18) +
attempt(() => Error.isError(v)).padEnd(9) +
attempt(() => Object.prototype.toString.call(v)),
);
} Output
value typeof isArray instanceof Array isError toString tag
[] object true true false [object Array]
new Uint8Array(2) object false false false [object Uint8Array]
{ length: 0 } object false false false [object Object]
new Proxy([], {}) object true true false [object Array]
new Error() object false false true [object Error]
new Proxy(error, {}) object false false false [object Object]
fake error object false false false [object Object]
tagged as Array object false false false [object Array]
null object false false false [object Null]
revoked proxy object TypeError TypeError false TypeError
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node checks-table.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
typeofsays'object'for every value here,nullincluded. It separates primitives and functions from objects, and nothing more.Array.isArraylooks through a proxy to its target, so a proxied array counts. A typed array is not anArray, and neither is an array-like object with alength;ArrayBuffer.isViewis the realm-proof test for typed arrays. For a revoked proxy,Array.isArraythrows aTypeError.instanceof Arrayagrees withArray.isArrayin this table only because every value was made in the same realm.Error.isErrorsaystrueonly for real error objects, the ones that carry the internal marker of an error. It does not look through proxies, and a duck-typed fake withname,messageandstackdoes not fool it.- The
toStringtag comes from internal markers or fromSymbol.toStringTag, sotagged as Arrayreports[object Array]. Use tags in logs, never for decisions.
Duck typing: checking what a value can do
Duck typing asks for the capability you are about to use, and the language itself works this way:
// Duck typing: await and Promise.resolve accept any object with a then method, promise or not.
const courier = {
then(onFulfilled) {
console.log(' then() was called by the engine');
onFulfilled('parcel delivered');
},
};
console.log(await courier);
console.log(await Promise.resolve(courier));
// The checks behind it: is there a callable then? Is there a callable Symbol.iterator?
const isThenable = (v) => v !== null && (typeof v === 'object' || typeof v === 'function') && typeof v.then === 'function';
const isIterable = (v) => v != null && typeof v[Symbol.iterator] === 'function';
console.log(isThenable(courier), isThenable(Promise.resolve(1)), isThenable({ then: 'after lunch' }));
console.log(isIterable('Pune'), isIterable(new Set([1])), isIterable({ length: 3 }), isIterable(null));
// The catch: an object whose then means something else is treated as a promise too.
class TrainRoute {
constructor(stops) {
this.stops = stops;
}
then(nextStop) {
return new TrainRoute([...this.stops, nextStop]); // a builder method that happens to be called then
}
}
const route = new TrainRoute(['Pune']).then('Lonavala');
console.log(route.stops);
const settled = await Promise.race([
Promise.resolve(route).then((value) => `resolved with ${typeof value}`),
new Promise((resolve) => setTimeout(() => resolve('still pending after 50 ms'), 50)),
]);
console.log(settled); Output
then() was called by the engine parcel delivered then() was called by the engine parcel delivered true true false true true false false [ 'Pune', 'Lonavala' ] still pending after 50 ms
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node thenable.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
awaitandPromise.resolveaccept thenables. When a promise is resolved with an object, the engine reads itsthenproperty, and if it is a function, calls it with two functions that settle the promise.courieris no promise, but awaiting it works.for…of, spread andArray.fromaccept any iterable: a value whoseSymbol.iteratorproperty is a function.- Check the capability exactly.
typeof value.then === 'function'is the test the engine applies;'then' in valuewould also accept{ then: 'after lunch' }, whichawaittreats as an ordinary value. - A method that happens to be called
thenis a trap.TrainRouteusesthenas a builder method, so awaiting a route makes the engine call it with resolve and reject functions it never calls, and the promise stays pending forever: the race shows it still waiting after 50 milliseconds.
Duck typing works across realms and across copies of a library, and it lets a small object stand in for a class in a test. What it cannot do is tell two unrelated classes with the same method names apart; when that matters, use a brand check, as the last sections show.
Symbol.hasInstance: letting a class answer instanceof
instanceof first looks for a Symbol.hasInstance method on its right side and, when there is one, returns that
method’s answer:
// Symbol.hasInstance lets a class answer instanceof itself, even for primitives.
class PinCode {
static [Symbol.hasInstance](value) {
return typeof value === 'string' && /^[1-9][0-9]{5}$/.test(value);
}
}
console.log('411001' instanceof PinCode, '041100' instanceof PinCode, 411001 instanceof PinCode);
// Every function inherits the default from Function.prototype, and that default cannot be overwritten by assignment.
const standard = Object.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance);
console.log('default writable:', standard.writable, '| configurable:', standard.configurable);
// Subclasses inherit a custom check, so it can say yes for the wrong class.
class Vehicle {
static [Symbol.hasInstance](value) {
return typeof value?.wheels === 'number';
}
}
class Bus extends Vehicle {}
const autoRickshaw = { wheels: 3 };
console.log('autoRickshaw instanceof Bus:', autoRickshaw instanceof Bus); Output
true false false default writable: false | configurable: false autoRickshaw instanceof Bus: true
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node has-instance.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
Every function inherits the default method from Function.prototype, and it performs the prototype walk of the
first section. The standard makes that default non-writable and non-configurable, so no code can replace it for every
function at once, but a class can define its own static method, as PinCode and Vehicle do.
- Primitives can pass.
'411001' instanceof PinCodeistrue, because the custom method runs before anything else looks at the left side. - Subclasses inherit the method.
BusgetsVehicle’s check, so any object with a number ofwheelscounts as aBus. A method that should only answer for its own class must testthis === Vehiclefirst. - Use it rarely. Readers expect
instanceofto mean “made by this class”. A function namedisPinCode(value)says what it checks and surprises nobody; keepSymbol.hasInstancefor a brand check that existinginstanceofcode must keep using.
Checks that survive two copies of a library
The cross-realm failure happens without any iframe when a program loads two copies of one package, for example two versions installed side by side. Each copy evaluates its own class:
// Two copies of one library, as when a project ends up with two versions of a package: each copy has its own class.
function loadMoneyLibrary() {
const BRAND = Symbol.for('example-money-library.Money'); // the same symbol in every copy and every realm
class Money {
#paise;
constructor(paise) {
this.#paise = paise;
this[BRAND] = true;
}
get paise() {
return this.#paise;
}
static isMoneyOfThisCopy(value) {
return typeof value === 'object' && value !== null && #paise in value;
}
static isMoney(value) {
return typeof value === 'object' && value !== null && value[BRAND] === true;
}
}
return { Money };
}
const copyA = loadMoneyLibrary();
const copyB = loadMoneyLibrary();
const price = new copyA.Money(4_999);
const checks = {
'instanceof copyB.Money': price instanceof copyB.Money,
'#paise in, copy B': copyB.Money.isMoneyOfThisCopy(price),
'Symbol.for brand, copy B': copyB.Money.isMoney(price),
'duck typing: paise is a number': typeof price.paise === 'number',
};
for (const [check, answer] of Object.entries(checks)) console.log(check.padEnd(31), answer); Output
instanceof copyB.Money false #paise in, copy B false Symbol.for brand, copy B true duck typing: paise is a number true
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node two-copies.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
The Node.js documentation calls one form of this the dual package hazard, and states that an instanceof comparison
of instances created by the two versions returns false. A private-field brand check fails too, because each
evaluation of a class body creates new private names. A key from Symbol.for is the same in every copy and every
realm, so a brand stored under it is recognised by both copies. It can be faked by any code that knows the key, so it
means “made by some copy of this library”, while #paise in value stays the strict check inside one copy. For errors,
libraries commonly combine Error.isError(err) with a check of err.name or of a code property they document.
Which check answers which question:
- Is it an array?
Array.isArray(value), in every realm and every copy. - Is it an error?
Error.isError(value), in every realm and every copy. - Is it a typed array or a
DataView?ArrayBuffer.isView(value), in every realm and every copy. - Is it a date, a map or a set? Call one of its methods, such as
Date.prototype.getTime.call(value), and catch theTypeError; this works in every realm and every copy. - Can I iterate it, or await it?
typeof value[Symbol.iterator] === 'function'andtypeof value.then === 'function', in every realm and every copy. - Was it made by my class?
#field in value, but only for objects of this copy of the class, in this realm. - Was it made by my library? A brand under a
Symbol.forkey works across realms and copies, but code that knows the key can fake it.
Exercise: checks for values from any realm
Exercise · Medium · JavaScript
Type checks that work for values from any realm
A settings library merges the options a page passes in, and some pages pass them from an iframe, where every object has that frame's own prototypes. Write two checks that give the same answer for values from any realm:
isPlainObject(value)istruefor objects made by an object literal,new Object(),Object.create(null)orObject.create(Object.prototype), andfalsefor everything else: arrays, functions, dates, maps, class instances,nulland primitives. In another realm, a plain object's prototype is that realm'sObject.prototype, not yours, but it still has no prototype of its own.isIterable(value)istruewhenfor…ofwould acceptvalue: strings, arrays, maps, sets, generators and any object with a callableSymbol.iterator, own or inherited. It isfalsefornull,undefined, numbers, plain objects and objects whoseSymbol.iteratoris not a function. It never throws.
isPlainObject({ city: 'Pune' }); // true
isPlainObject(Object.create(null)); // true
isPlainObject([1, 2]); // false
isIterable('Pune'); // true: strings are iterable
isIterable({ length: 2 }); // false: a length is not enough
The sample tests stand in for another realm with objects whose prototypes are not this realm's. The starter code uses instanceof and constructor: run the tests to see which values it gets wrong.
Starter code · checks.mjs
/** True for objects made by {}, new Object(), Object.create(null) or Object.create(Object.prototype), in any realm. */
export function isPlainObject(value) {
// Replace these lines with your code: instanceof and constructor depend on this realm.
return value instanceof Object && value.constructor === Object;
}
/** True when for…of accepts value: it has a callable Symbol.iterator. */
export function isIterable(value) {
return value instanceof Array || value instanceof Map || value instanceof Set;
} The sample tests · checks.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { isPlainObject, isIterable } from './checks.mjs';
// Stand-ins for values from another realm: their prototypes are not this realm's.
const otherObjectPrototype = Object.create(null);
const otherArrayPrototype = Object.create(otherObjectPrototype);
otherArrayPrototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
const otherRealmObject = Object.create(otherObjectPrototype);
otherRealmObject.city = 'Pune';
const otherRealmArray = Object.setPrototypeOf(['Pune', 'Goa'], otherArrayPrototype);
test('plain objects made in this realm', () => {
assert.equal(isPlainObject({}), true);
assert.equal(isPlainObject({ city: 'Pune', pin: 411001 }), true);
assert.equal(isPlainObject(new Object()), true);
assert.equal(isPlainObject(Object.create(Object.prototype)), true);
});
test('objects with no prototype count as plain', () => {
const dictionary = Object.create(null);
dictionary.constructor = 'not a function';
assert.equal(isPlainObject(Object.create(null)), true);
assert.equal(isPlainObject(dictionary), true);
});
test('a plain object from another realm counts as plain', () => {
assert.equal(isPlainObject(otherRealmObject), true);
});
test('arrays, functions, built-ins and class instances are not plain', () => {
class Settings {}
assert.equal(isPlainObject([]), false);
assert.equal(isPlainObject(otherRealmArray), false);
assert.equal(isPlainObject(() => {}), false);
assert.equal(isPlainObject(new Date(0)), false);
assert.equal(isPlainObject(new Map()), false);
assert.equal(isPlainObject(/pune/), false);
assert.equal(isPlainObject(new Settings()), false);
assert.equal(isPlainObject(Object.create({ inherited: true })), false);
});
test('null and primitives are not plain objects', () => {
for (const value of [null, undefined, 0, 'Pune', true, 10n, Symbol('s')]) {
assert.equal(isPlainObject(value), false, `isPlainObject(${String(value)})`);
}
});
test('strings, collections and generators are iterable', () => {
function* stops() {
yield 'Pune';
}
assert.equal(isIterable('Pune'), true);
assert.equal(isIterable(''), true);
assert.equal(isIterable([1, 2]), true);
assert.equal(isIterable(new Map()), true);
assert.equal(isIterable(new Set()), true);
assert.equal(isIterable(new Uint8Array(2)), true);
assert.equal(isIterable(stops()), true);
assert.equal(isIterable({ [Symbol.iterator]: () => [][Symbol.iterator]() }), true);
});
test('an array from another realm is iterable', () => {
assert.equal(isIterable(otherRealmArray), true);
assert.deepEqual([...otherRealmArray], ['Pune', 'Goa']);
});
test('values for…of refuses are not iterable, and nothing throws', () => {
assert.equal(isIterable(null), false);
assert.equal(isIterable(undefined), false);
assert.equal(isIterable(42), false);
assert.equal(isIterable({}), false);
assert.equal(isIterable({ length: 2, 0: 'a', 1: 'b' }), false);
assert.equal(isIterable({ [Symbol.iterator]: 42 }), false);
assert.equal(isIterable(Object.create(null)), false);
}); A hint
For isPlainObject, rule out null and everything whose typeof is not 'object', then look at Object.getPrototypeOf(value): it is either null, or an object whose own prototype is null. That test does not mention this realm's Object.prototype, so it works for any realm. For isIterable, a value is iterable when it is not null or undefined and typeof value[Symbol.iterator] is 'function'; Symbol.iterator is the same symbol in every realm.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Self-check
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
Fresher · Why does Array.isArray exist when instanceof Array works?
Because instanceof Array answers a different question. It asks whether this realm’s Array.prototype is on the
value’s prototype chain, and that goes wrong in both directions. An array made in an iframe, a worker or a Node.js
vm context has its own realm’s Array.prototype, so instanceof Array says false for a real array; and an
object made with Object.create(Array.prototype) says true although it is not an array. Array.isArray asks
whether the object itself is an array, whichever realm made it, and it also looks through proxies of arrays. It says
false for array-like objects such as { length: 2 } and for typed arrays. Use it for every “is this an array”
check; Error.isError, added in ECMAScript 2026, does the same for errors.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A charting library validates its options with value instanceof Array and value instanceof Date. A customer embeds the chart in a same-origin iframe and passes options that the parent page created, and valid options are rejected. Explain why, and rewrite the validation so that it works for values from any realm.
-
Senior to staff
After a dependency upgrade, an app's error handler stops recognising errors from an HTTP client: err instanceof HttpError is false, although the stack shows the client threw an HttpError. The lockfile shows two versions of the client installed. Explain the mechanism, the immediate fix, and how the library should make its errors recognisable.
Key takeaways
value instanceof Casks whetherC.prototypeis onvalue’s prototype chain right now; it says nothing about which constructor made the value.- Each window, iframe, worker and
vmcontext is a realm with its own built-ins, soinstanceoffails for values made in another realm. Array.isArray,Error.isError,ArrayBuffer.isViewand calls of built-in methods check the object itself and work in every realm;typeofonly separates objects from primitives and functions.- Well-known symbols and
Symbol.forkeys are shared by all realms, which is why duck typing onSymbol.iteratororthenworks everywhere; never name a methodthenunless it behaves like a promise’s. Symbol.hasInstancelets a class answerinstanceof, primitives included, and subclasses inherit it: prefer a namedisXfunction.- Two copies of a library have two classes:
instanceofand#field infail across them, and aSymbol.forbrand does not.
References
- ECMAScript 2026 Language Specification: InstanceofOperator (Ecma International)
- ECMAScript 2026 Language Specification: OrdinaryHasInstance (Ecma International)
- ECMAScript 2026 Language Specification: IsArray (Ecma International)
- ECMAScript 2026 Language Specification: Error.isError (Ecma International)
- ECMAScript 2026 Language Specification: Realms (Ecma International)
- ECMAScript 2026 Language Specification: Well-Known Symbols (Ecma International)
- ECMAScript 2026 Language Specification: Symbol.for (Ecma International)
- ECMAScript 2026 Language Specification: CreateResolvingFunctions (thenables) (Ecma International)
- instanceof (MDN Web Docs)
- VM (executing JavaScript), Node.js v24 (OpenJS Foundation (Node.js))
- Dual package hazard (Node.js package examples) (OpenJS Foundation (Node.js))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress