Type checking in JavaScript

JavaScript's built-in type checks are famously unreliable, so these four helpers do the job properly. Array.isArray for arrays, typeof === "function" for functions, an explicit null guard for objects, and a prototype comparison for plain objects.

Easy type checking prototypes

The question

Usually a rapid-fire warm-up: "how do you check if something is an array?", then "an object?", then "a plain object?". Each answer is one line and each one has a trap, which is what makes it a good filter early in an interview.

The solution

Four small functions, each avoiding a specific well-known hole in the language.

function isArray(value) {
  return Array.isArray(value);
}

function isFunction(value) {
  return typeof value === "function";
}

function isObject(value) {
  // Arrays and functions are objects. null is not, despite its typeof.
  return value !== null && (typeof value === "object" || typeof value === "function");
}

function isPlainObject(value) {
  if (value === null || typeof value !== "object") return false;
  // A plain object is one built from Object, or one with no prototype at all.
  const prototype = Object.getPrototypeOf(value);
  return prototype === null || prototype === Object.prototype;
}

How it works

The tests it has to pass

This is the part most explanations leave out. "It works" is not a claim you can check by reading. These are the 3 cases Practice Pad runs against your solution, and the implementation above passes all of them.

Easy3 cases · the four predicates

recognises arrays and functions with their own checks

expectEqual([isArray([]), isArray({}), isFunction(() => {}), isFunction({})], [true, false, true, false]);

counts arrays and functions as objects, but not null

expectEqual([isObject([]), isObject(() => {}), isObject(null)], [true, true, false]);

identifies an object literal as a plain object

expectEqual([isPlainObject({}), isPlainObject({ a: 1 })], [true, true]);

Edge cases to watch out for

Relying on typeof for objects

typeof null === "object" is the language's most famous wart, and it makes every unguarded typeof x === "object" check wrong for null. The guard isn’t optional.

Using instanceof Array

It compares against the current realm's Array, so an array created in an iframe, a worker or a Node vm context fails. Array.isArray has no such problem.

Treating any object as a plain object

A Date, a Map, a class instance and an array are all objects. If the point of the check is "can I safely spread this and treat the keys as data", the prototype comparison is what you actually want.

The other common approach is Object.prototype.toString.call(value), which returns strings like "[object Array]" and "[object Date]". It’s realm-safe and distinguishes more built-ins than typeof, which is why lodash uses it. It can be spoofed via Symbol.toStringTag, but rarely by accident.

Follow up questions

Why is typeof null "object"?

A bug from the first version of JavaScript that was never fixed because too much code depends on it. Values were tagged by their low bits, and the null pointer shared the object tag. Every object check therefore needs an explicit null guard.

Why use Array.isArray instead of instanceof Array?

instanceof compares against the current realm's constructor, so an array created in an iframe, worker or Node vm context has a different Array and fails. Array.isArray inspects the internal slot and works everywhere.

What counts as a plain object?

Usually an object literal, or one created with Object.create(null): something whose prototype is Object.prototype or nothing at all. Class instances, Dates, Maps and arrays are objects but not plain, which matters when you intend to treat the keys as data.

Is Object.prototype.toString.call still the best type check?

It’s the most general one, distinguishing Date, RegExp, Map and more in a single expression, and it’s realm-safe. For the specific checks here the dedicated forms are clearer and faster; toString is what you reach for when you need one function that names any type.

Practise this with the tests running

Practice Pad runs these 3 cases against your own solution, in a real editor, on your Mac. Twenty-four questions, and an optional AI panel that explains why a test failed.

Download for macOS Free. Requires macOS 12 or later on Apple Silicon.

See all 24 questions