Array.isArray()

ES5+

Determines whether the passed value is an Array.

Syntax

Array.isArray(value)

Parameters

value any

The value to be checked

Return Value

boolean

true if the value is an Array; otherwise, false

Examples

JavaScript
console.log(Array.isArray([1, 2, 3]));
console.log(Array.isArray('hello'));
console.log(Array.isArray({length: 3}));
Output:
// true false false

📌 When to Use

Use Array.isArray() at every point where a value of uncertain shape must be treated differently depending on whether it is an array: validating parsed JSON from an API (is data.items the expected list, or did the server send a single object this time?), normalizing flexible function parameters that accept "one or many", writing library code that branches between array and scalar handling, and guarding utility functions against malformed input. It exists because the obvious checks are all broken in some way: typeof returns "object" for arrays (and for null), and instanceof Array fails across realm boundaries - an array created inside an iframe or a Node vm context inherits from THAT realm's Array.prototype, so mainRealmArray instanceof Array is true but iframeArray instanceof Array is false, even though both are genuine arrays. Array.isArray() checks the internal, branding-level definition of arrayhood (the same one the spec uses) and answers correctly regardless of origin, and it even returns true for a Proxy whose target is an array. Note what it does NOT do: TypedArrays (Float64Array and friends), NodeLists, strings, and arguments objects are all false - it detects real Array instances only, not "things you can index". For those, ArrayBuffer.isView() or duck-typing on length serve different questions.

⚠️ Common Mistakes

Testing with typeof: there is no "array" result - typeof [] is "object", indistinguishable from plain objects, null (also "object"), Dates, and RegExps. Validation code built on typeof lets objects masquerade as arrays and then fails deep inside a for...of or a map() call, far from the inadequate check that admitted them.

Relying on instanceof Array in code that can receive values from another realm - an iframe, a worker boundary that structured-clones, a Node vm sandbox, or a test harness like older jsdom setups. Each realm has its own Array constructor, so a perfectly real array from elsewhere fails the check. The bug is invisible until deployment meets an embed or plugin scenario; Array.isArray() is immune by specification.

Duck-typing on .length. Strings, NodeLists, arguments objects, typed arrays, jQuery collections, and any object someone gave a length property all pass the test, and each behaves differently from a real array afterwards - strings are immutable, NodeLists lack map(). Length says "indexable-ish", not "Array"; only the branded check says Array.

Assuming Array.isArray() validates contents. It brands the container only: [1, {}, null, "x"] passes exactly as readily as a clean number list. Schema-level guarantees need element checks on top - arr.every(item => typeof item === "number") - or a validation library; the isArray() gate is merely step one.

Expecting TypedArrays to qualify: Array.isArray(new Float64Array(8)) is false, because typed arrays are a different specification type with different semantics (fixed length, numeric elements). Code that should accept both needs an explicit second branch via ArrayBuffer.isView() or an instanceof check against the specific typed-array class.

✅ Best Practices

Make Array.isArray() the unconditional habit for array detection - it is never wrong where instanceof is sometimes wrong, costs nothing extra, and reads identically. There is no scenario in modern JavaScript where instanceof Array is the better array check, so the rule requires zero judgment calls.

Exploit its built-in TypeScript narrowing: inside if (Array.isArray(x)), a string | string[] parameter narrows to string[] (and to string in the else branch) with no custom type guard - the compiler ships this refinement natively, making the one-or-many parameter pattern fully type-safe.

Normalize at the entrance, not throughout: const list = Array.isArray(input) ? input : [input] as the first line lets every subsequent line assume "array", collapsing what would be scattered dual-shape handling into one check. APIs that return an object for single results and an array for multiple are the classic candidates.

Guard external data before calling array methods on it: JSON.parse() output shaped by a server you do not control deserves if (!Array.isArray(payload.items)) throw new Error("items must be an array") - a named, early failure instead of "payload.items.map is not a function" three modules later.

Distinguish arrays before generic object handling in serializers, cloners, and pretty-printers: check Array.isArray() first, then typeof x === "object" && x !== null, because arrays satisfy the object test too and would otherwise fall into the wrong branch and come back as {"0": ..., "1": ...}.

⚡ Performance Notes

Array.isArray() is O(1) and about as cheap as a function call gets: V8 reduces it to an internal instance-type inspection of the object's map (its hidden class tag), with no prototype-chain walk - which also makes it faster than instanceof, whose semantics require traversing the prototype chain, and dramatically faster than the old Object.prototype.toString.call(x) === "[object Array]" idiom, which allocates and compares a string. The one input with measurable extra cost is a Proxy, where the spec requires unwrapping to the target, still trivial in absolute terms. All of this means the check is free for practical purposes: calling it per element inside large loops, per message in a hot IPC handler, or per invocation of a widely-used utility adds nothing visible to a profile. Performance is therefore never a reason to skip validation here - if anything, the guard SAVES time by failing fast with a clear error rather than letting a non-array crawl through several stack frames before crashing.

🌍 Real World Example

Normalizing Function Input to Always Be an Array

Two production-grade guard patterns. processItems() implements one-or-many normalization: callers pass a single value or a list, the ternary wraps the scalar case into a one-element array, and from that line down there is exactly one code path - no duplicated logic, no "if array do X else do X slightly differently" drift. In TypeScript the same check narrows the parameter type automatically. validateData() shows the fail-fast counterpart for untrusted input: asserting the shape at the boundary converts a would-be "tags.map is not a function" somewhere downstream into an immediate, searchable error message naming the actual contract violation. Together they cover the two reasons this check appears in real codebases: flexibility on purpose, and defense against data that breaks its promises.

function processItems(input) {
  // Normalize input to always be an array
  const items = Array.isArray(input) ? input : [input];

  return items.map(item => transform(item));
}

// Works with both:
processItems('single');           // processes ['single']
processItems(['a', 'b', 'c']);    // processes ['a', 'b', 'c']

// Type checking in validation
function validateData(data) {
  if (!Array.isArray(data.tags)) {
    throw new Error('tags must be an array');
  }
  // data.tags is guaranteed to be an array here
}

Related Methods