Checking types reliably
Choose the right JavaScript type check for null, arrays, class instances, object labels, and errors without being fooled by old quirks or cross-realm values.
- 01Read quick checks correctlyExplain why typeof null is object and wrap it safely.
- 02Pick the right inspectorUse instanceof, Array.isArray, and object tags for their best jobs.
- 03Handle errors honestlyFeature-detect Error.isError and know the fallback limits.
The reliable-check mindset
INTERACTIVEJavaScript lets values travel far: from form fields, JSON, libraries, iframes, tests, and your own classes. By the time a value reaches your function, you often need to ask a small question before touching it: is this really an array? Did someone pass null? Is this thrown value an Error or just a random object?
The trick is that JavaScript does not have one perfect type-check button. It has several inspectors, and each answers a different question. typeof is a fast glance. instanceof checks a family tree. Array.isArray is a specialist. The Object.prototype.toString label reader is useful but can be customized. Error.isError is the new error specialist, but it is not everywhere yet.
A parcel label gives a quick clue about what is inside. Some checks are stronger than others, so use the one that answers your question.
- In real life: Reading the parcel label
- In JavaScript:
typeof: fast, broad labels - In real life: Checking who sent the parcel
- In JavaScript:
instanceof: is this prototype in the chain? - In real life: Checking for the array mark
- In JavaScript:
Array.isArray: arrays across realms - In real life: Reading the printed label
- In JavaScript:
Object.prototype.toString.call(value)
Where the analogy stops: Parcel labels can be wrong. In JavaScript, Symbol.toStringTag can customize labels, so a label is not proof.
Start with a real matrix. Read across one row to compare checks for a single value. Read down one column to see what a check is good at. The Error.isError column is feature-detected in your browser after mount, because this new API is missing in Node 22 and in many browsers.
const values = [null, [], new Date(), new Error("boom")];for (const value of values) { console.log(typeof value); console.log(value instanceof Error); console.log(Array.isArray(value)); console.log(Object.prototype.toString.call(value));}| Value | typeof | instanceof Object | instanceof Array | instanceof Error | Array.isArray | Object tag | Error.isError |
|---|---|---|---|---|---|---|---|
null | object | false | false | false | false | [object Null] | unavailable |
undefined | undefined | false | false | false | false | [object Undefined] | unavailable |
[] | object | true | true | false | true | [object Array] | unavailable |
{} | object | true | false | false | false | [object Object] | unavailable |
new Date() | object | true | false | false | false | [object Date] | unavailable |
/re/ | object | true | false | false | false | [object RegExp] | unavailable |
new Map() | object | true | false | false | false | [object Map] | unavailable |
function() {} | function | true | false | false | false | [object Function] | unavailable |
class Demo {} | function | true | false | false | false | [object Function] | unavailable |
new Error('boom') | object | true | false | true | false | [object Error] | unavailable |
Promise.resolve(1) | object | true | false | false | false | [object Promise] | unavailable |
5n | bigint | false | false | false | false | [object BigInt] | unavailable |
Symbol() | symbol | false | false | false | false | [object Symbol] | unavailable |
NaN | number | false | false | false | false | [object Number] | unavailable |
{ [Symbol.toStringTag]: 'Custom' } | object | true | false | false | false | [object Custom] | unavailable |
nullOnly strict equality or a helper should call this null.undefinedThe only value whose typeof is undefined.[]Arrays are objects, but Array.isArray is the trusted check.{}Plain objects need shape checks, not just labels.
Checking for Error.isError after mount…
Error.isError is feature-detected only in the browser after hydration.typeof null: the famous old quirk
STEP THROUGHYou met the typeof table in Data types and saw the operator again in The rest of the operators. Most of its answers are handy: "string", "number", "boolean", "bigint", "symbol", "undefined", "function", or "object". The historic surprise is typeof null.
In early engines, values were represented with small type tags. Object values used one tag, and the null pointer ended up looking like that object tag. The exact engine details are old history, but the result became web-compatible behavior: typeof null still returns "object". It does not mean null has properties or behaves like an object.
Predict the four-line helper result before stepping through the real run.
script
const quickCheck = typeof value;const exactCheck = value === null;const type = getType(value);console.log(quickCheck, exactCheck, type);Use value === null when you specifically mean null. If your code intentionally accepts both null and undefined as “missing,” you may see value == null, but that is a broader missing-value check, not an exact null check.
A small helper can make display labels friendlier without pretending JavaScript changed its rules:
function getType(value) {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
return typeof value;
}The order is the lesson: check null before raw typeof, and check arrays before calling every object an object.
instanceof: a family-tree check
STEP THROUGHFor this lesson, the precise mental model is one sentence: object instanceof Constructor asks whether Constructor.prototype is somewhere in the object’s prototype chain. The prototype chain itself is the next stage’s topic; here, you only need that one relationship.
instanceof checks whether a family name appears in an object’s tree. An iframe or test VM has its own tree, so matching names can still come from different constructors.
- In real life: A person’s family tree
- In JavaScript: An object’s prototype chain
- In real life: A family name in that tree
- In JavaScript:
Constructor.prototype - In real life: Adoption paperwork changing the tree
- In JavaScript:
Object.setPrototypeOfchanging results - In real life: Another family’s tree
- In JavaScript: Another realm with its own constructors
Where the analogy stops: Objects are not people: JavaScript can change an object's prototype chain, and later lessons cover the deeper mechanics.
Step through this example twice. First keep the chain. Then switch line 4 so the object’s prototype is replaced with null, and predict whether the second check can change.
Switch line 4, predict the second instanceof, then step through the real result.
script
const basket = new Basket();const before = basket instanceof Basket;// keep Basket.prototype in the chainconst after = basket instanceof Basket;console.log(before, after);instanceof has three practical pitfalls:
- Primitives are not wrapper objects:
"x" instanceof Stringisfalse. Usetypeof value === "string"for string primitives. - Cross-realm values have different constructors. An array or error from an iframe can fail your page’s
instanceof Arrayorinstanceof Errorcheck. - Prototype changes affect the result. That is uncommon in everyday application code, but it proves what the operator reads.
Array.isArray: the trusted array inspector
Arrays are objects, so typeof [] returns "object". They are often created by the same realm as your code, so [] instanceof Array usually returns true in quick experiments. But “usually” is not a reliable API boundary.
Use Array.isArray(value) when the real question is “is this a JavaScript Array?” It recognizes arrays from other realms, including iframe-like environments, and it rejects plain objects that merely have 0 and length properties.
The lesson’s regression tests use node:vm contexts to prove the cross-realm behavior: an array from another context fails value instanceof Array in the current context, but Array.isArray(value) returns true. The same tests show the parallel problem for errors.
The common review comment is simple: if you see typeof value === "object" or value instanceof Array guarding array methods, ask for Array.isArray(value) instead.
Object.prototype.toString and Symbol.toStringTag
STEP THROUGHEvery object inherits a toString method, but calling a value’s own toString is not consistent: arrays join their items, dates format dates, and custom objects can override the method. The classic label reader is the original method, borrowed directly:
Object.prototype.toString.call(value)That call returns labels such as [object Array], [object Null], [object Date], [object Map], and [object Promise]. The Symbols lesson previewed well-known symbols; here you see one in action. Symbol.toStringTag lets built-ins and your own objects customize the label.
Step through the labels, then ask whether a label is proof or just a useful clue.
script
const arrayTag = tag.call([]);const nullTag = tag.call(null);const mapTag = tag.call(new Map());const custom = { [Symbol.toStringTag]: "Custom" };const customTag = tag.call(custom);console.log(arrayTag, nullTag, mapTag, customTag);Because Symbol.toStringTag is customizable, an object can claim a friendly label. That is useful for display and debugging, but not proof that the object has the behavior of an array, map, or error. Prefer dedicated checks when they exist.
Error.isError: new, useful, and not everywhere
INTERACTIVEError detection has the same cross-realm problem as arrays. An error created in another iframe or VM context inherits from that realm’s Error.prototype, not yours. That means err instanceof Error can be false even for a genuine error.
Error.isError(value) is a very recent addition designed to answer “is this really an Error object?” more reliably, including across realms and against objects that merely fake name and message. It is not in Node 22 and is missing in many browsers, so client code must feature-detect it before calling it.
function isErrorReliable(value) { if (typeof Error.isError === "function") { return Error.isError(value); } return Object.prototype.toString.call(value) === "[object Error]";}[object Error]trueunavailableWaiting until after mount avoids server/browser feature-detection mismatches.
Symbol.toStringTag can fool a tag-based check.Until the API is broadly available, a common fallback is:
function isErrorFallback(value) {
return Object.prototype.toString.call(value) === "[object Error]";
}That fallback is useful because real cross-realm errors commonly keep the [object Error] label. But it is limited: labels can be customized with Symbol.toStringTag, so do not treat it as an unfakeable security boundary.
Which check should you use?
INTERACTIVEIn real code, the right check follows from the job. A validator, a parser, and an error reporter do not need the same answer. This table is the everyday cheat sheet.
| Goal | Use | Why |
|---|---|---|
| Exactly null | value === null | Avoid the typeof null quirk and avoid matching undefined by accident. |
| Arrays | Array.isArray(value) | Works across realms and rejects array-like plain objects. |
| Errors | Error.isError(value) when available; fallback tag with limits | Designed for cross-realm Error detection; fallback is useful but not perfect. |
| Dates in your realm | value instanceof Date plus validity checks | Checks the constructor relationship; also check !Number.isNaN(value.getTime()) for a valid date. |
| Plain objects | value !== null && typeof value === "object" && !Array.isArray(value) | Start by excluding null and arrays; add prototype checks if you require literals only. |
| Functions | typeof value === "function" | Functions get a special typeof result. |
| Your class instances | value instanceof YourClass | Clear when the value should come from your constructor in the same realm. |
Now sort each situation by the check that most directly answers it.
- Is this value a string primitive?
- Is this callable value a function?
- Was this Date created by this page's Date constructor?
- Was this object made by your
Invoiceclass? - Is this value an Array, even if it came from an iframe?
- Should I avoid calling an array a plain object?
- What built-in label does
nullhave? - Do I want a display label such as
[object Map]?
Sort each scenario by the check that answers the question with the fewest blind spots.
Common misconceptions
“typeof null means null is an object.”
It means JavaScript preserves a legacy result. null has no properties and should be checked with value === null.
“typeof [] should be enough for arrays.”
Arrays are objects, so raw typeof cannot distinguish them. Use Array.isArray.
“instanceof proves where a value came from forever.”
It reads the current prototype chain. Cross realms and prototype changes can affect the answer.
“Object.prototype.toString cannot lie.”
It reads a label, and Symbol.toStringTag can customize that label. A label is useful, not absolute proof.
“A thrown thing is always an Error.”
JavaScript lets code throw any value. Error detection helps you decide how to report it, but your catch blocks should still handle strange thrown values gracefully.
Practice: choose the right check
5 EXERCISESPredict the three console lines without running the code first.
console.log(typeof null);
console.log(null === null);
console.log(Array.isArray([]));The console prints object, then true, then true. Raw typeof is quirky for null, but strict equality and Array.isArray answer their questions directly.
getType helperWrite the helper in your console or editor. It should return "null" for null, "array" for arrays, and otherwise return JavaScript’s typeof result.
function getType(value) {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
return typeof value;
}
console.log(getType(null));
console.log(getType([]));
console.log(getType(5n));function getType(value) {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
return typeof value;
}
console.log(getType(null));
console.log(getType([]));
console.log(getType(5n));The helper returns null, array, and bigint. The special cases come first; everything else can use typeof.
This function tries to avoid non-arrays, but its guard is impossible. What check should replace it?
function firstItem(value) {
if (typeof value !== "array") return "not an array";
return value[0];
}function firstItem(value) {
if (!Array.isArray(value)) return "not an array";
return value[0];
}
console.log(firstItem(["Ada"]));
console.log(firstItem({ 0: "Ada", length: 1 }));Replace the impossible typeof comparison with Array.isArray(value). The fixed function returns Ada for an array and not an array for an array-like object.
Before Error.isError is available everywhere, write a fallback using the object tag and state its limitation.
function isErrorFallback(value) {
return Object.prototype.toString.call(value) === "[object Error]";
}
console.log(isErrorFallback(new Error("boom")));
console.log(isErrorFallback({ name: "Error", message: "fake" }));function isErrorFallback(value) {
return Object.prototype.toString.call(value) === "[object Error]";
}
console.log(isErrorFallback(new Error("boom")));
console.log(isErrorFallback({ name: "Error", message: "fake" }));The fallback prints true for a real Error and false for a plain object with similar properties. It is useful, but remember that Symbol.toStringTag can fake labels.
In one sentence, explain why an array or error from another realm can fail an instanceof check in your realm.
Across realms, the value may be linked to another realm's Array.prototype or Error.prototype. Your realm's instanceof Array or instanceof Error can be false because the constructors and prototypes are different objects.
Quiz: check your understanding
7 QUESTIONSYour first try counts, but every answer explains the reasoning. Look for the question being asked before picking the operator.
Question 1 of 7What does
typeof nullreturn?Read the code, then predictconsole.log(typeof null);Choose an answer to see the explanation.
Question 2 of 7Which check is the reliable way to detect arrays, including arrays from another realm?
Choose an answer to see the explanation.
Question 3 of 7What does this print?
Read the code, then predictconsole.log("x" instanceof String);Choose an answer to see the explanation.
Question 4 of 7What is
instanceofchecking in the ordinary case?Choose an answer to see the explanation.
Question 5 of 7What label does this custom tag print?
Read the code, then predictconst custom = { [Symbol.toStringTag]: "Array" }; console.log(Object.prototype.toString.call(custom));Choose an answer to see the explanation.
Question 6 of 7When
Error.isErroris missing, what should a lesson helper honestly say about the fallback?Choose an answer to see the explanation.
Question 7 of 7What does this getType helper print?
Read the code, then predictfunction getType(value) { if (value === null) return "null"; if (Array.isArray(value)) return "array"; return typeof value; } console.log(getType([]));Choose an answer to see the explanation.
Key takeaways
typeofis best for primitive labels and functions; it famously returns"object"fornull.- Use
value === nullfor null andArray.isArray(value)for arrays. instanceofasks whether a constructor’s prototype is in an object’s prototype chain, so realms and prototype changes matter.Object.prototype.toString.call(value)reads useful labels, butSymbol.toStringTagmeans labels can be customized.- Feature-detect
Error.isError. Use a tag fallback only with its limits clearly understood.
One-liner.
A reliable type check asks the smallest exact question: null, array, prototype relationship, object label, or real error.
Up next: The prototype chain, where the family tree behind property lookup and instanceof gets its full explanation.