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.
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
Array.isArraybeatsinstanceof Arraybecause it works across realms: an array from an iframe or a Node vm fails the instanceof check but passes this one.isObjectcounts functions, which matches the specification: a function is a callable object. It also has to excludenullexplicitly, becausetypeof nullis"object".isPlainObjectcompares the prototype toObject.prototypeornull, which rejects class instances, arrays, Dates and Maps while accepting object literals andObject.create(null).- None of these use
instanceof, which is the recurring theme: it’s realm-sensitive and can be defeated bySymbol.hasInstance.
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.
✓ 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.
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.