Back to JavaScript
JavaScript
easy
mid

How do you use typeof and instanceof, and what is reliable type checking in JavaScript?

`typeof` returns a primitive type string but is wrong for null ("object") and arrays. `instanceof` checks the prototype chain but fails across realms (iframes). The reliable cross-realm check is `Object.prototype.toString.call(x)` → "[object Array]" etc. For primitives use `typeof`; for arrays `Array.isArray`; for null `=== null`; for plain objects there's no perfect check.

4 min read·~6 min to think through

JavaScript's type system has more rough edges than most realize. The four common checks each have failure modes.

typeof

js
typeof undefined        // "undefined"
typeof "x"              // "string"
typeof 42               // "number"
typeof 42n              // "bigint"
typeof true             // "boolean"
typeof Symbol()         // "symbol"
typeof (() => {})       // "function"
typeof {}               // "object"
typeof []               // "object"   ← surprising
typeof null             // "object"   ← historical bug

Useful for primitives. Useless for distinguishing arrays / null / plain objects.

instanceof

js
[] instanceof Array               // true
new Date() instanceof Date        // true

Walks the prototype chain. Two failure modes:

  1. Cross-realm — arrays from another iframe / Worker fail instanceof Array because their prototype is a different Array.prototype.
  2. Custom Symbol.hasInstance — classes can override how instanceof decides.

Array.isArray

js
Array.isArray([])                                // true
Array.isArray(iframeArray)                        // true — works cross-realm

The reliable check for arrays. Always prefer this over instanceof Array.

Object.prototype.toString

js
Object.prototype.toString.call(undefined)   // "[object Undefined]"
Object.prototype.toString.call(null)        // "[object Null]"
Object.prototype.toString.call([])          // "[object Array]"
Object.prototype.toString.call(new Date())  // "[object Date]"
Object.prototype.toString.call(/x/)         // "[object RegExp]"
Object.prototype.toString.call(new Map())   // "[object Map]"

The closest thing to a "true class" check, and it's cross-realm safe.

Plain object?

There's no perfect check. Common heuristics:

js
function isPlainObject(v) {
  if (v === null || typeof v !== "object") return false;
  const p = Object.getPrototypeOf(v);
  return p === null || p === Object.prototype;
}

Works for most cases — {} and Object.create(null) return true; class instances, arrays, Maps return false.

null check

Just x === null. Don't use typeof here.

NaN

Number.isNaN(x) — not isNaN() (which coerces). Or x !== x (only NaN is not equal to itself).

Function check

typeof fn === "function". Catches arrow, regular, async, class constructors (which are also functions).

In TypeScript

Most of this disappears with proper types. But user input, JSON parse, third-party libs, postMessage — all still need runtime guards. Use Zod / Valibot to validate at boundaries instead of hand-rolling.

Decision table

Want to checkUse
Primitive typetypeof
null=== null
ArrayArray.isArray
Plain objectprototype check above
Date / Map / RegExp / specific built-inObject.prototype.toString.call or instanceof (same realm)
External / boundary dataZod / Valibot schema
NaNNumber.isNaN

Interview framing

"typeof for primitives — it's wrong about null (returns 'object') and lumps arrays in with objects. instanceof walks the prototype chain but fails across realms — iframes have their own Array.prototype. Array.isArray is the cross-realm-safe array check. For built-ins like Date/Map/RegExp, Object.prototype.toString.call(x) is the reliable check. Plain object has no perfect check — I use the prototype-equals-Object.prototype heuristic. At system boundaries (JSON, postMessage, user input), I use Zod to validate instead of hand-rolling type checks."

Follow-up questions

  • Why does typeof null return 'object'?
  • When have you been bitten by cross-realm instanceof?
  • How would you implement deepEqual?

Common mistakes

  • Using typeof to detect arrays.
  • Using instanceof in a multi-realm app (iframes, workers).
  • Hand-rolling type validation at API boundaries instead of using a schema.

Performance considerations

  • All checks are O(1). Object.prototype.toString.call is slightly slower than typeof but negligible. Heavy validation goes through schema libraries (Zod) which cache parsers.

Edge cases

  • typeof of an undeclared variable returns 'undefined' without throwing.
  • Symbol.hasInstance can lie about instanceof.
  • BigInt vs Number — different types, no implicit conversion.

Real-world examples

  • Zod / Yup / Valibot in API layers, lodash's isPlainObject / isArray, browser polyfill code, library type guards.

Senior engineer discussion

Seniors know the cross-realm issue, prefer `Array.isArray` and `Object.prototype.toString` for reliability, and push validation to the boundary with schema libraries rather than runtime guards scattered through the codebase.

Related questions