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 typeof correctlyExplain why
typeof nullgives "object", and check fornullthe safe way. - 02Pick the right checkUse
instanceof,Array.isArray, and object labels for the jobs they do best. - 03Check for real errorsCheck that
Error.isErrorexists before you call it, and know how its fallback can be fooled.
Pick the check that fits your question
INTERACTIVEValues reach your functions from many places: form fields, JSON data, libraries, tests, and your own classes. Before you use a value, you often need to ask a small question about it. For example:
- Is this really an array?
- Did someone pass
null? - Is this thrown value a real
Error, or just some object?
JavaScript has no single check that answers every question. It has several checks, and each one answers a different question:
typeofgives a quick, rough type name, such as"string"or"object".instanceofasks whether an object was made by a certain class.Array.isArrayasks one thing only: is this an array?Object.prototype.toStringreads a label that every value has. Objects can change their label.Error.isErrorasks whether a value is a real error. It is very new, so many browsers don't have it yet.
A parcel arrives at your door. You can shake it, check who sent it, look for a seal, or read its label. Each one tells you something different. Pick the one that answers your question.
- In real life: Shaking the parcel to guess what is inside
- In JavaScript:
typeof: a quick, rough answer - In real life: Checking who sent the parcel
- In JavaScript:
instanceof: which class made this object? - In real life: Looking for the shop's official seal
- In JavaScript:
Array.isArray: is this really an array? - In real life: Reading the label the sender wrote
- In JavaScript:
Object.prototype.toString.call(value): a label the object can change
The table below runs every check on many values. Read across one row to see how one value does in every check. Read down one column to see what one check is good at.
The Error.isError column fills in after the page loads in your browser. It shows results only if your browser has this new check. Node 22 and many browsers don't have it yet.
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 label | 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 |
nulltypeof says object. Check it with value === null, or a helper.undefinedThe only value whose typeof is undefined.[]Arrays are objects. Array.isArray is the check to trust.{}A plain object. Its label alone doesn't tell you what it holds.
Checking whether your browser has Error.isError…
Error.isError column fills in after the page loads in your browser.typeof null: an old mistake that stayed
STEP THROUGHtypeof is an operator. You write it before a value, and it gives back the value's type as text. You met it in the Data types lesson and again in The rest of the operators. It gives one of these answers: "string", "number", "boolean", "bigint", "symbol", "undefined", "function", or "object".
Most answers are what you expect. One is a surprise: typeof null gives "object".
In the first JavaScript engines, each value carried a small type number. Objects used one number. null happened to look like that object number. So typeof null gave "object".
Many websites came to depend on that answer, so it was never fixed. It does not mean null is an object. null has no properties at all.
Guess the three printed answers, then step through the real run.
script
const quickCheck = typeof value;const exactCheck = value === null;const type = getType(value);console.log(quickCheck, exactCheck, type);null goes into three checks. typeof gives "object", which is an old mistake JavaScript keeps. value === null gives an exact true. The getType helper checks for null first, so it gives "null".To check for null, write value === null. It is true only for null.
Some code writes value == null instead. That is true for both null and undefined. So it checks for "missing", not for null exactly.
A small helper function can give friendlier names. It checks the special cases first, then uses typeof for everything else:
function getType(value) {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
return typeof value;
}The order matters:
- Line 2 checks for
nullbeforetypeofcan say"object". - Line 3 checks for arrays, because
typeof []also says"object". - Line 4 uses
typeoffor every other value.
instanceof: was this made by that class?
STEP THROUGHEvery object has a hidden link to another object, called its prototype. The object can borrow methods from its prototype. That prototype can link to another object, and so on. This line of links is the object's prototype chain. The next stage covers it in full. Here you only need one fact about it.
When you write new Basket(), the new object links to Basket.prototype. Then basket instanceof Basket asks one question: is Basket.prototype anywhere along basket's chain of links?
instanceof looks for one family name in an object's family tree. If someone changes the tree, the answer changes too. And two families can share a surname but still be different families.
- In real life: A person’s family tree
- In JavaScript: An object’s prototype chain
- In real life: One family name you look for in the tree
- In JavaScript:
Constructor.prototype - In real life: Adoption papers that change the tree
- In JavaScript:
Object.setPrototypeOfchanges the answer - In real life: A different family with the same surname
- In JavaScript: Another realm, with its own
ArrayandError
Step through this example twice. First, keep the link. Then change line 4 so it removes the link with Object.setPrototypeOf(basket, null). Before you step, guess whether the second check changes.
Choose what line 4 does, guess the second answer, then step through the real run.
script
const basket = new Basket();const before = basket instanceof Basket;// keep Basket.prototype in the chainconst after = basket instanceof Basket;console.log(before, after);basket instanceof Basket follows the basket's link and looks for Basket.prototype. On the left the link is there, so the answer is true. On the right, line 4 cut the link with Object.setPrototypeOf(basket, null), so the same check gives false.instanceof has three traps:
- Plain values are not objects.
"x" instanceof Stringisfalse, because"x"is a plain string, not an object made byString. To check for a string, usetypeof value === "string". - Values from another realm fail. A realm is a separate JavaScript world with its own built-ins, such as its own
ArrayandError. An iframe (a page shown inside your page) is one example. An array made in an iframe links to the iframe'sArray.prototype, not yours. So your page'sinstanceof Arraycheck givesfalse. - Changing the link changes the answer. You saw this on line 4. Real apps rarely do this, but it shows that
instanceofonly follows the links.
Array.isArray: the check to trust for arrays
Arrays are objects, so typeof [] gives "object". That doesn't tell an array apart from any other object.
[] instanceof Array is usually true in small tests, because the array and the check come from the same realm. But "usually" is not good enough when you check data you don't control.
Array.isArray(value) asks exactly one question: is this value a real array? It says true for arrays from any realm, including an iframe. It says false for plain objects that only have a 0 and a length.
This lesson's tests use Node's node:vm module to make a second realm. An array made there gives false for value instanceof Array in our realm. But Array.isArray(value) gives true. Errors made there have the same problem with instanceof Error.
So when you see typeof value === "object" or value instanceof Array used to check for an array, use Array.isArray(value) instead.
const list = { 0: "tea", length: 1 };
console.log(Array.isArray(list));
console.log(Array.isArray(["tea"]));Line 1 makes a plain object with a 0 and a length. It looks like an array, but it has no array methods. Line 2 prints false. Line 3 checks a real array and prints true.
list is a plain object with a 0 and a length. It looks like an array, but it is not one, so Array.isArray(list) gives false. ["tea"] is a real array, so it gives true.This matters when data comes from a form, a library, or another window. "It has numbered keys" only describes how the object looks. Array.isArray asks whether it really is an array.
Object.prototype.toString and Symbol.toStringTag
STEP THROUGHEvery object has a toString method. It turns the object into text. But each kind of object does this its own way:
- Arrays join their items:
[1, 2].toString()gives"1,2". - Dates write out the date.
- Your own objects can replace the method with their own.
The original toString lives on Object.prototype. It does something more useful for type checks: it reads a label. To use it on any value, add .call(value). .call runs a function and tells it which value to work on:
Object.prototype.toString.call(value)This gives text such as [object Array], [object Null], [object Date], [object Map], and [object Promise]. The last word is the label.
An object can set its own label with Symbol.toStringTag. It is one of the well-known symbols from the Symbols lesson. Built-in objects such as Map use it, and your objects can too.
Step through the labels. Line 1 saves the original method as tag, so the later lines stay short.
Step through the labels. Then ask: is a label 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);tag, gives text like "[object Array]". The last word is the label. For the array on line 2, JavaScript fills in Array. The custom object on line 5 sets its own label with [Symbol.toStringTag]: "Custom" (the card says tag for short). Line 6 reads that label back.Any object can set Symbol.toStringTag, so an object can claim any label, even "Array". A label helps when you print or debug values. It does not prove the object works like an array, a map, or an error. When a check made for the job exists, such as Array.isArray, use that.
One more focused check is worth knowing: the check for NaN. NaN means "not a number". It is the number value you get when a math step fails, such as Number("tea").
console.log(Number.isNaN("tea"));
console.log(isNaN("tea"));
console.log(Number.isNaN(NaN));- Line 1 asks whether the string
"tea"is the valueNaN. It is not, so it printsfalse. - Line 2 uses the older global
isNaN. It first converts"tea"to a number, which givesNaN. So it printstrue. - Line 3 checks the real
NaNvalue and printstrue.
Use Number.isNaN when you mean "is this value NaN?" Use the global isNaN only when you want that conversion, and leave a comment that says so.
Error.isError: new, useful, and not everywhere yet
INTERACTIVEErrors have the same realm problem as arrays. An error made in another iframe links to that iframe's Error.prototype, not yours. So err instanceof Error can be false, even for a real error.
Error.isError(value) is a very new check. It answers "is this a real error object?" It works across realms. It also says false for plain objects that only copy an error's name and message.
Node 22 and many browsers don't have it yet. So your code must first check that it exists, and only then call it. This is called feature detection. The helper below does it on lines 2 and 3. The playground runs the helper in your browser after the page loads.
function isErrorReliable(value) { if (typeof Error.isError === "function") { return Error.isError(value); } return Object.prototype.toString.call(value) === "[object Error]";}[object Error]trueunavailableWaiting for the page to load, so the check runs in your browser.
Error.isError. But an object with a faked Symbol.toStringTag label can fool it.Until every browser has it, you can fall back to the label check:
function isErrorFallback(value) {
return Object.prototype.toString.call(value) === "[object Error]";
}This fallback works because real errors keep the [object Error] label, even when they come from another realm. But Symbol.toStringTag can change a label. So the fallback is a good guess, not proof. Don't rely on it for security.
Which check should you use?
INTERACTIVEThe right check depends on what your code needs to know. Here are three real jobs:
- telling plain objects apart from arrays and
null - checking that a value has what your code will use
- checking data from a server
A plain object is one made with curly brackets, like { name: "Asha" }. This helper accepts plain objects only:
function isPlainObject(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
const profile = { name: "Asha" };
console.log(isPlainObject(profile));
console.log(isPlainObject(["Asha"]));
console.log(isPlainObject(null));- Line 2 turns away
null, values that are not objects, and arrays. - Line 3 reads the object's prototype with
Object.getPrototypeOf. - Line 4 accepts only two prototypes. One is
Object.prototype, which every curly-bracket object links to. The other isnull, for objects made withObject.create(null). Those have no prototype at all, and code often uses them as simple dictionaries. - Line 6 makes a plain object. Lines 7 to 9 print
truefor it,falsefor an array, andfalsefornull.
Often you don't need lines 3 and 4. If any object that is not null and not an array will do, line 2's checks are enough. Add the prototype check only when you really need plain objects.
function canShowName(value) {
return typeof value === "object" && value !== null && "name" in value;
}
const user = { name: "Asha", role: "editor" };
console.log(canShowName(user));
console.log(canShowName({ role: "editor" }));This helper does not ask which class made the value. It asks two things on line 2: is the value an object that is not null, and does it have a name? Line 5 prints true for user. Line 6 prints false for an object with no name.
This style is called duck typing, after the saying "if it walks like a duck and quacks like a duck, it is a duck." You accept a value because it has what your code will use.
function readProfile(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return "invalid profile";
const prototype = Object.getPrototypeOf(value);
if (prototype !== Object.prototype && prototype !== null) return "invalid profile";
if (typeof value.name !== "string") return "invalid profile";
if (typeof value.age !== "number" || !Number.isInteger(value.age)) return "invalid profile";
return value.name + ", " + value.age;
}
const response = { name: "Asha", age: 28 };
console.log(readProfile(response));
console.log(readProfile({ name: "Asha", age: "28" }));Data from a server can have any shape, so check it as soon as it arrives. Lines 1 to 8 define readProfile:
- Lines 2 to 4 make sure the value is a plain object.
- Line 5 checks that
nameis a string. - Line 6 checks that
ageis a whole number.Number.isIntegeristrueonly for whole numbers. - Line 7 builds the text to show.
Line 10 prints Asha, 28. Line 11 prints invalid profile, because that age is the string "28", not a number. After this one check, the rest of your app can trust the shape of the data.
The table below is a quick guide. Each row starts with something you might need to know. Read across to find the check to use, and why it works.
| What you want to know | The check to use | Why it works |
|---|---|---|
Is it exactly null? | value === null | It avoids the typeof null mistake. It does not match undefined by accident. |
| Is it an array? | Array.isArray(value) | It works across realms. It says false for objects that only look like arrays. |
| Is it a real error? | Error.isError(value) if it exists. Otherwise, the label check. | Error.isError works across realms. The label check is a good fallback, but labels can be faked. |
| Is it a date made on this page? | value instanceof Date, plus a check that the date is valid. | instanceof checks the link to Date.prototype. !Number.isNaN(value.getTime()) checks that the date is not "Invalid Date". |
| Is it a plain object? | value !== null && typeof value === "object" && !Array.isArray(value) | It turns away null and arrays first. Add a prototype check if you need curly-bracket objects only. |
| Is it a function? | typeof value === "function" | typeof gives functions their own answer, "function". |
| Was it made by your own class? | value instanceof YourClass | It is clear and correct when the object comes from your own code on this page. |
Now sort each question under the check that answers it best.
- Is this value a plain string, like
"hi"? - Is this value a function I can call?
- Was this date made by this page's
Date? - Was this object made by your
Invoiceclass? - Is this value an array, even if it came from an iframe?
- How do I keep arrays out of my plain-object code?
- What built-in label does
nullhave? - Do I want text to show, such as
[object Map]?
Put each question under the check that answers it most directly, with the fewest surprises.
Common misconceptions
“typeof null means null is an object.”
It doesn't. It is an old mistake that JavaScript kept so old websites keep working. null has no properties. Check for it with value === null.
“typeof [] is enough to find arrays.”
Arrays are objects, so typeof [] gives "object", the same as for any other object. Use Array.isArray.
“instanceof tells you for sure where a value came from.”
It only follows the object's links as they are right now. A value from another realm, or an object whose link was changed, can give a different answer.
“Object.prototype.toString can't be fooled.”
It reads a label, and Symbol.toStringTag can change that label. A label is a useful clue, not proof.
“Anything thrown is an Error.”
JavaScript lets code throw any value: a string, a number, or a plain object. Checking for real errors helps you decide how to report a problem. But your catch blocks should still cope with strange thrown values.
Practice: choose the right check
5 EXERCISESGuess the three lines the console prints. Then run the code to check.
console.log(typeof null);
console.log(null === null);
console.log(Array.isArray([]));The console prints object, then true, then true. typeof gives its old wrong answer for null. The exact check and Array.isArray each answer their own question directly.
getType helperWrite the helper in your console or editor. It should return "null" for null, "array" for arrays, and the typeof answer for every other value.
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 two special cases come first. Every other value uses typeof.
This function should return the first item of an array, and turn everything else away. But its check is wrong, so it turns away real arrays too. Which 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 typeof check with !Array.isArray(value). The fixed function returns Ada for an array, and not an array for an object that only looks like an array.
Not every browser has Error.isError yet. Write a fallback that uses the label check. Then say in one sentence how it could be fooled.
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 that copies an error's properties. Remember its weak spot: Symbol.toStringTag can fake the label.
In one sentence, explain why an array or error from another realm can give false for instanceof in your realm.
An array from another realm links to that realm's Array.prototype. Your realm's Array.prototype is a different object. So instanceof Array in your realm gives false. The same happens with errors and instanceof Error.
Quiz: check your understanding
7 QUESTIONSYour first try counts, and every answer explains why. Before you pick, ask what each question really wants to know.
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 finds arrays you can trust, even 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 does
instanceofcheck in the usual case?Choose an answer to see the explanation.
Question 5 of 7What label does this 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 is true about the label 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 good for plain values, such as strings and numbers, and for functions. It gives"object"fornull, which is an old mistake.- Use
value === nullfornull, andArray.isArray(value)for arrays. instanceofasks whether a class's prototype is in an object's chain of links. Other realms and changed links can change the answer.Object.prototype.toString.call(value)reads a useful label. Objects can change that label withSymbol.toStringTag.- Check that
Error.isErrorexists before you call it. The label check is a fallback, and it can be fooled.
One-liner.
A good type check asks one exact question: is it null, an array, made by this class, labelled this way, or a real error?
Up next: The prototype chain. It explains the chain of links that property lookup and instanceof follow.