JAVASCRIPT / ARRAYS
Creating arrays and checking with Array.isArray
Create arrays with literals, Array.of, Array.from, and spread, and reliably detect them with Array.isArray instead of typeof or instanceof.
What you will learn
- Build arrays from strings, Sets, Maps, and {length: n} objects with Array.from
- Avoid the new Array(n) single-number overload by using Array.of or Array.from
- Use Array.isArray instead of typeof, because typeof [] is "object"
- Know why instanceof Array fails for an array made in another realm
Understanding Creating arrays and checking with Array.isArray
A literal is the shortest and clearest way to make an array: [10, 20, 30] creates one object whose element keys are the canonical numeric strings "0", "1", "2". What makes it an array is not the bracket syntax but an internal marker the engine sets when the object is created, which gives it exotic length behaviour: assigning to an index at or past length grows length by itself. Array.from, Array.of, and spread all produce that same kind of object, and they exist because the data you start with is frequently a string, a Set, an iterator, or an object that merely has a length.
The Array constructor behaves differently depending on how many arguments it receives, and that inconsistency is exactly why Array.of was added. Two or more arguments become the elements, but a single number is read as a length, so new Array(7) gives you an array whose length is 7 and which contains no elements at all. Array.of(7) always means one element holding the number 7, and Array.from({ length: 7 }, () => 0) is how you get seven real slots, because from walks the indices 0 through length - 1 and stores the mapping function's result at each one.
typeof cannot answer "is this an array" because the language gives typeof a single answer for every non-callable object: "object". instanceof Array looks promising but asks a different question, namely whether this realm's Array.prototype sits on the value's prototype chain, so an array created inside an iframe or a Node vm context fails even though it is a genuine array. Array.isArray checks the internal marker instead, which is why it is true for arrays from any realm and for subclasses of Array, and false for array-likes such as arguments or { length: 2 } that only resemble one.
const literal = [10, 20, 30];
const fromChars = Array.from('abc');
const computed = Array.from({ length: 3 }, (_, i) => i * 5);
const single = Array.of(7);
const sized = new Array(7);
console.log(JSON.stringify(literal));
console.log(JSON.stringify(fromChars));
console.log(JSON.stringify(computed));
console.log(JSON.stringify(single), single.length);
console.log(sized.length, 0 in sized);
console.log(typeof literal, typeof {});
console.log(Array.isArray(literal), Array.isArray('abc'), Array.isArray({ length: 3 }));An array is an ordinary object carrying an internal array marker and exotic length behaviour, so identifying one requires Array.isArray rather than typeof or the prototype chain.
Worked examples
The single-number constructor trap
Shows how Array(n) differs from Array.of and why a pre-sized array is not a filled one.
console.log(Array(3).length, Array(3, 4).length);
console.log(Array.of(3).length, Array.of(3, 4).length);
const n = 3;
const holes = new Array(n);
const real = Array.from({ length: n }, () => 0);
console.log(JSON.stringify(holes));
console.log(JSON.stringify(real));
let counted = 0;
holes.forEach(() => counted++);
console.log('forEach ran', counted, 'times');Example explained
Line 1Array(3) reads a lone number as a length, while Array(3, 4) reads its arguments as elements, so one call shape means two different things.
Line 2Array.of never overloads: every argument is an element, which is why Array.of(3) has length 1.
Line 3JSON.stringify prints the empty slots as null, a signal that no value was ever stored at those indices.
Line 4forEach reports zero runs because it visits only indices that exist, so new Array(n) is a poor way to pre-size a list.
Array-likes and subclasses
Demonstrates that Array.isArray tests the internal array marker, not the shape or the class of a value.
function collect() {
console.log(Array.isArray(arguments), arguments.length);
const real = Array.from(arguments);
console.log(Array.isArray(real), JSON.stringify(real));
}
collect('a', 'b');
class Stack extends Array {}
const s = new Stack();
console.log(Array.isArray(s), s instanceof Array, typeof s);
console.log(Object.prototype.toString.call([]), Object.prototype.toString.call({}));Example explained
Line 1arguments has a length and numeric keys but fails Array.isArray, because it is a plain object with no array marker.
Line 2Array.from(arguments) copies those indexed properties into a real array, which then passes the check.
Line 3A Stack instance is still an array exotic object, so Array.isArray returns true while typeof still reports "object".
Line 4Object.prototype.toString.call is the older trick that separates [object Array] from [object Object]; Array.isArray replaced it.
Building arrays from a Set or Map
Turns iterable collections into real arrays and shows that the collections themselves are not arrays.
const tags = ['js', 'css', 'js', 'html', 'css'];
const unique = Array.from(new Set(tags));
const spread = [...new Set(tags)];
console.log(JSON.stringify(unique));
console.log(unique.length, Array.isArray(unique), Array.isArray(new Set(tags)));
console.log(JSON.stringify(spread) === JSON.stringify(unique));
console.log(JSON.stringify(Array.from(new Map([['a', 1], ['b', 2]]))));Example explained
Line 1new Set(tags) removes duplicates while keeping insertion order, but it is a Set, so Array.isArray on it is false.
Line 2Array.from consumes the Set's iterator and produces a genuine array that passes the check.
Line 3Spreading into brackets consumes the same iterator, so the two results are identical.
Line 4Array.from on a Map yields [key, value] pairs because that is what the Map iterator hands out.
Important notes
Array.isArray says nothing about contents: [] and new Array(3) both return true, so test length separately when you need a non-empty array.
Array.from on a string uses the string iterator, so 'a\u{1F600}'.length is 3 while Array.from('a\u{1F600}').length is 2, because surrogate pairs stay together.
Common mistakes
Writing new Array(5) expecting [5] or five zeros: you get length 5 with no elements, so forEach and map skip every index and the loop body never runs.
Testing typeof value === 'array': that string is never produced by typeof, so the branch is dead code and arrays silently fall into the plain-object path.
Treating a false result from Array.isArray as "no data": arguments and a NodeList hold real items and just need Array.from first, otherwise calling .map on them throws a TypeError.
Try it yourself
Change, predict, then run
In a browser console, write toArray(value) that returns the value unchanged when Array.isArray(value) is true, uses Array.from when the value is iterable or has a numeric length, and Array.of otherwise. Call it with [1, 2], 'hi', new Set([1, 1, 2]), document.querySelectorAll('div'), and 42, logging the result and its length each time.
Open the JavaScript workspaceCheck your understanding
A page receives data from a same-origin iframe, and data is a real array that was created by code running inside that iframe. Which check reliably reports it as an array?
- typeof data === 'array'
- data instanceof Array
- Array.isArray(data)
- typeof data.length === 'number'
Show answer
Array.isArray inspects the internal marker set when the array object was created, so it does not care which realm created it. instanceof Array is the tempting answer, but it only asks whether the current page's Array.prototype is on the prototype chain, and the iframe's array inherits from the iframe's own Array.prototype, so it returns false; typeof never yields 'array', and a numeric length only proves the value is array-like.