completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

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.

By the end, you can
  • 01
    Read typeof correctlyExplain why typeof null gives "object", and check for null the safe way.
  • 02
    Pick the right checkUse instanceof, Array.isArray, and object labels for the jobs they do best.
  • 03
    Check for real errorsCheck that Error.isError exists before you call it, and know how its fallback can be fooled.

Pick the check that fits your question

INTERACTIVE

Values 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:

  • typeof gives a quick, rough type name, such as "string" or "object".
  • instanceof asks whether an object was made by a certain class.
  • Array.isArray asks one thing only: is this an array?
  • Object.prototype.toString reads a label that every value has. Objects can change their label.
  • Error.isError asks whether a value is a real error. It is very new, so many browsers don't have it yet.
Real-life analogyFinding out what a parcel is

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.

Every check on many values
The code behind the tablePop out in the code editor (opens in a new tab)JavaScript
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));}
Real answers15 rows
Valuetypeofinstanceof Objectinstanceof Arrayinstanceof ErrorArray.isArrayObject labelError.isError
nullobjectfalsefalsefalsefalse[object Null]unavailable
undefinedundefinedfalsefalsefalsefalse[object Undefined]unavailable
[]objecttruetruefalsetrue[object Array]unavailable
{}objecttruefalsefalsefalse[object Object]unavailable
new Date()objecttruefalsefalsefalse[object Date]unavailable
/re/objecttruefalsefalsefalse[object RegExp]unavailable
new Map()objecttruefalsefalsefalse[object Map]unavailable
function() {}functiontruefalsefalsefalse[object Function]unavailable
class Demo {}functiontruefalsefalsefalse[object Function]unavailable
new Error('boom')objecttruefalsetruefalse[object Error]unavailable
Promise.resolve(1)objecttruefalsefalsefalse[object Promise]unavailable
5nbigintfalsefalsefalsefalse[object BigInt]unavailable
Symbol()symbolfalsefalsefalsefalse[object Symbol]unavailable
NaNnumberfalsefalsefalsefalse[object Number]unavailable
{ [Symbol.toStringTag]: 'Custom' }objecttruefalsefalsefalse[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.
Try it yourself

Checking whether your browser has Error.isError…

Real JavaScript answers for common values. The Error.isError column fills in after the page loads in your browser.

typeof null: an old mistake that stayed

STEP THROUGH

typeof 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.

Step through: typeof gives "object" for null
Step 0 of 5Ready
Your turn: follow the blue line

Guess the three printed answers, then step through the real run.

Running in
  1. script
Next: line 1
const quickCheck = typeof value;const exactCheck = value === null;const type = getType(value);console.log(quickCheck, exactCheck, type);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
A box called value holds null. typeof value gives "object", an old mistake. value === null gives true, an exact check. getType(value) gives "null", from the helper.nullvaluetypeof valuevalue === nullgetType(value)"object"true"null"mistakeexacthelper
The same 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".
Safe null checks

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:

A helper that names null and arraysPop out in the code editor (opens in a new tab)JavaScript
function getType(value) {
  if (value === null) return "null";
  if (Array.isArray(value)) return "array";
  return typeof value;
}

The order matters:

  • Line 2 checks for null before typeof can say "object".
  • Line 3 checks for arrays, because typeof [] also says "object".
  • Line 4 uses typeof for every other value.

instanceof: was this made by that class?

STEP THROUGH

Every 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?

Real-life analogyLooking for a name in a family tree

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.setPrototypeOf changes the answer
In real life: A different family with the same surname
In JavaScript: Another realm, with its own Array and Error

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.

Keep or cut the link, then check again
Step 0 of 6Ready
Your turn: follow the blue line

Choose what line 4 does, guess the second answer, then step through the real run.

Running in
  1. script
Next: line 1
const basket = new Basket();const before = basket instanceof Basket;// keep Basket.prototype in the chainconst after = basket instanceof Basket;console.log(before, after);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose what line 4 does before the second check.

Changing line 4 starts the replay again. Guess the value of after.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Left: line 4 keeps the link. The basket object links to Basket.prototype, so basket instanceof Basket is true. Right: line 4 cuts the link. The basket has no link to Basket.prototype, so the check is false.line 4 keeps the linkBasket.prototypelinkbaskettrueline 4 cuts the linkBasket.prototypeno linkbasketfalse
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 String is false, because "x" is a plain string, not an object made by String. To check for a string, use typeof value === "string".
  • Values from another realm fail. A realm is a separate JavaScript world with its own built-ins, such as its own Array and Error. An iframe (a page shown inside your page) is one example. An array made in an iframe links to the iframe's Array.prototype, not yours. So your page's instanceof Array check gives false.
  • Changing the link changes the answer. You saw this on line 4. Real apps rarely do this, but it shows that instanceof only 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.

The lesson's tests prove it

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.

An object that looks like an array is not onePop out in the code editor (opens in a new tab)JavaScript
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.

Left: Array.isArray(list). The list object holds 0: "tea" and length: 1. It only looks like an array, so the answer is false. Right: Array.isArray(["tea"]). A real array, so the answer is true.Array.isArray(list)0: "tea"length: 1listfalseonly looks like oneArray.isArray(["tea"])["tea"]a real arraytrue
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 THROUGH

Every 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:

Use the original label reader on any valuePop out in the code editor (opens in a new tab)JavaScript
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: reading object labels
Step 0 of 7Ready
Your turn: follow the blue line

Step through the labels. Then ask: is a label proof, or just a useful clue?

Running in
  1. script
Next: line 1
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);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Top: tag.call on an empty array gives the label Array, so the result is "[object Array]". Bottom: the custom object holds its own tag, "Custom", so tag.call(custom) gives "[object Custom]".[]an arrayArray"[object Array]"built-in labeltag: "Custom"customCustom"[object Custom]"the object chose it
The label reader, 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.
A label is a clue, not proof

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").

Two NaN checks give different answersPop out in the code editor (opens in a new tab)JavaScript
console.log(Number.isNaN("tea"));
console.log(isNaN("tea"));
console.log(Number.isNaN(NaN));
  • Line 1 asks whether the string "tea" is the value NaN. It is not, so it prints false.
  • Line 2 uses the older global isNaN. It first converts "tea" to a number, which gives NaN. So it prints true.
  • Line 3 checks the real NaN value and prints true.

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

INTERACTIVE

Errors 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.

Check for a real error
Use Error.isError if it exists, otherwise the labelPop out in the code editor (opens in a new tab)JavaScript
function isErrorReliable(value) {  if (typeof Error.isError === "function") {    return Error.isError(value);  }  return Object.prototype.toString.call(value) === "[object Error]";}
Chosen valuereal
Object label[object Error]
Label fallback saystrue
Error.isError saysunavailable
Try it yourself

Waiting for the page to load, so the check runs in your browser.

The label fallback helps in browsers without 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:

A fallback error check that reads the labelPop out in the code editor (opens in a new tab)JavaScript
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?

INTERACTIVE

The 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:

Tell plain objects apart from arrays and nullPop out in the code editor (opens in a new tab)JavaScript
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 is null, for objects made with Object.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 true for it, false for an array, and false for null.

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.

Check for what you need, not who made itPop out in the code editor (opens in a new tab)JavaScript
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.

Check server data where it enters your codePop out in the code editor (opens in a new tab)JavaScript
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 name is a string.
  • Line 6 checks that age is a whole number. Number.isInteger is true only 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.

Which check to use for each question
What you want to knowThe check to useWhy it works
Is it exactly null?value === nullIt 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 YourClassIt 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.

Pick the right type check
  • 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 Invoice class?
  • 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 null have?
  • Do I want text to show, such as [object Map]?
Try it yourself
0 of 8 correct

Put each question under the check that answers it most directly, with the fewest surprises.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

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 EXERCISES
Exercise 1 · Warm-upPredict three checks

Guess the three lines the console prints. Then run the code to check.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(typeof null);
console.log(null === null);
console.log(Array.isArray([]));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeWrite a friendly getType helper

    Write the helper in your console or editor. It should return "null" for null, "array" for arrays, and the typeof answer for every other value.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    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));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix the array check

      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?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function firstItem(value) {
        if (typeof value !== "array") return "not an array";
        return value[0];
      }

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeWrite a fallback error check

        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.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function isErrorFallback(value) {
          return Object.prototype.toString.call(value) === "[object Error]";
        }
        console.log(isErrorFallback(new Error("boom")));
        console.log(isErrorFallback({ name: "Error", message: "fake" }));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeExplain why an array from an iframe fails

          In one sentence, explain why an array or error from another realm can give false for instanceof in your realm.

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Your first try counts, and every answer explains why. Before you pick, ask what each question really wants to know.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does typeof null return?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(typeof null);

              Choose an answer to see the explanation.

            2. Question 2 of 7Which check finds arrays you can trust, even arrays from another realm?

              Choose an answer to see the explanation.

            3. Question 3 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log("x" instanceof String);

              Choose an answer to see the explanation.

            4. Question 4 of 7What does instanceof check in the usual case?

              Choose an answer to see the explanation.

            5. Question 5 of 7What label does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const custom = { [Symbol.toStringTag]: "Array" };
              console.log(Object.prototype.toString.call(custom));

              Choose an answer to see the explanation.

            6. Question 6 of 7When Error.isError is missing, what is true about the label fallback?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does this getType helper print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function 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

            • typeof is good for plain values, such as strings and numbers, and for functions. It gives "object" for null, which is an old mistake.
            • Use value === null for null, and Array.isArray(value) for arrays.
            • instanceof asks 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 with Symbol.toStringTag.
            • Check that Error.isError exists 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.

            CompleteFrontend Clear concepts. Working examples.