skip to content

In JavaScript, what do `Array.from`, `Array.of`, `Array(n)` and array spread each produce, and which one do you use to build an array of n computed values?

level: middleimportance: should knowfreq 45%

answer

  1. length set, elements absent
  2. callbacks skip what was never there
  3. array-like counts as a source
  4. one argument means length, not element

basics

~10 s

Array(n) makes a length-n array with no elements at all, so array methods have nothing to visit. Array.from({length: n}, fn) fills those positions, Array.of(n) makes a single-element array, and spread converts anything iterable.

solid answer

~40 s

`Array(3)` is the odd one out: it creates an array of length 3 containing holes — no own index properties — so `Array(3).map((_, i) => i)` returns three holes and the callback never runs. `Array.of(3)` exists to remove that ambiguity: it always treats its arguments as elements, giving `[3]`, where `Array(3)` and `Array(3, 4)` mean different things. To build n values, `Array.from({length: 3}, (_, i) => i)` gives `[0, 1, 2]` — `Array.from` accepts array-likes as well as iterables, reads each index (absent ones read as `undefined`), and applies the optional mapping function while building, with no intermediate array. Spread, `[...source]`, only accepts iterables, which is why `[...Array(3)]` works — the array iterator yields `undefined` for holes — while `[...{length: 3}]` throws.

code

javascript · 12 lines
javascript
console.log(Array(3).length, Object.keys(Array(3)).length); // 3 0
console.log(Array(3).map((_, i) => i).length);              // 3, but callback never ran
console.log(Array.from({ length: 3 }, (_, i) => i));        // [0, 1, 2]
console.log([...Array(3)].map((_, i) => i));                // [0, 1, 2]
console.log(Array.of(3), Array(3, 4));                      // [3] [3, 4]

const shared = Array(2).fill([]);
shared[0].push('x');
console.log(shared[1]);                                     // ['x'] — same array
const distinct = Array.from({ length: 2 }, () => []);
distinct[0].push('x');
console.log(distinct[1]);                                   // []

go deeper

for a junior

Know that Array(3) gives length 3 with nothing in it, and that Array.from({length: 3}, (_, i) => i) is the idiom for building a numbered array. Recognize spread as the way to turn a string or Set into an array.

for a middle

Explain holes: no own index property, so map and forEach skip them while fill and Array.from write to every position. State the iterable-vs-array-like split that decides whether spread or Array.from applies.

for a senior

Catch the shared-reference bug in Array(n).fill([]) during review, and prefer the mapping-function form of Array.from when each slot needs its own object. Know that Array.from with mapFn avoids an intermediate array on large inputs.

for a principal

Set the house idiom so seeded arrays are built one way across the codebase, and be able to justify avoiding the bare Array constructor entirely in shared utilities, where a single numeric argument silently changes meaning.

## Four ways to get an array, and they are not interchangeable ### `Array(n)` — length without elements Called with a single numeric argument, the `Array` constructor sets `length` and creates nothing else: ```js const a = Array(3); a.length; // 3 Object.keys(a); // [] — no own index properties 0 in a; // false a.map((_, i) => i); // holes, callback never invoked ``` Those empty positions are holes. Array methods such as `map`, `forEach` and `filter` skip them, so the array looks like it has three slots but behaves as if it has none. Called with two or more arguments the same constructor means something completely different — `Array(3, 4)` is `[3, 4]` — and that inconsistency is a genuine footgun in generic code. ### `Array.of(...values)` — arguments are always elements `Array.of` was added precisely to remove the single-number special case: ```js Array.of(3); // [3] Array.of(3, 4); // [3, 4] Array.of(); // [] ``` It is the safe choice when the values come from a variable you did not author, so a lone number cannot silently become a length. ### `Array.from(source, mapFn?)` — the general converter `Array.from` accepts two kinds of input. If the source has an iterator it is iterated; otherwise it is treated as an **array-like** — any object with a `length` property, whose indexes `0 … length - 1` are read, missing ones producing `undefined`. That second path is what makes the length-object idiom work: ```js Array.from({ length: 3 }); // [undefined, undefined, undefined] Array.from({ length: 3 }, (_, i) => i); // [0, 1, 2] Array.from({ length: 3 }, () => []); // three distinct arrays Array.from('abc'); // ['a', 'b', 'c'] Array.from(new Set([1, 1, 2])); // [1, 2] ``` The optional second argument is a mapping function applied as each element is produced, so no intermediate array is allocated — and, unlike `map` over `Array(3)`, it runs for every position because `Array.from` reads indexes directly rather than skipping holes. The `() => []` form is the correct way to build n *distinct* nested values; `Array(3).fill([])` would put the *same* array in all three slots. `Array.from` is also the standard way to turn an array-like into a real array so array methods become available — the classic case being the `arguments` object inside a non-arrow function. ### Spread — iterables only ```js [...'abc']; // ['a', 'b', 'c'] [...new Set([1, 1])]; // [1] [...Array(3)]; // [undefined, undefined, undefined] [...{ length: 3 }]; // TypeError: object is not iterable ``` Spread requires an iterable, full stop. `[...Array(3)]` works because arrays are iterable and the array iterator walks every index from 0 to `length`, producing `undefined` for a hole — which is why the result is dense and `.map` on it now runs three times. That two-step idiom is equivalent to, but less direct than, `Array.from({length: 3}, fn)`. ## Choosing between them | Goal | Use | | --- | --- | | n computed values | `Array.from({length: n}, (_, i) => …)` | | n identical primitives | `Array(n).fill(0)` — `fill` writes real elements, unlike `map` | | Wrap arbitrary arguments as elements | `Array.of(...values)` | | Convert an iterable | spread or `Array.from` | | Convert an array-like without an iterator | `Array.from` only | `Array(n).fill(x)` deserves the footnote: `fill` writes to every index regardless of holes, so it does produce a dense array — but only of one repeated value, and if `x` is an object every slot shares it. ## Interview framing The question usually arrives as "why doesn't `Array(5).map((_, i) => i)` work?" The complete answer names holes, explains that array callbacks skip them, gives `Array.from({length: 5}, (_, i) => i)` as the direct fix and `[...Array(5)].map(...)` as the workaround that densifies first, and mentions `Array.of` as the reason the single-number constructor case exists to be worked around at all.

  • Why does `Array(3).fill(0)` produce real elements while `Array(3).map(() => 0)` does not?
    `fill` writes to every index from start to end regardless of whether a property exists there, so it converts holes into real elements. `map` visits only existing own index properties, preserving holes in the result and never invoking the callback for them. It is a difference in how the two methods treat absent indexes, not in the array they start from.
  • What breaks if you build a grid with `Array(3).fill([])`?
    All three slots hold the *same* array, so pushing into `grid[0]` appears in `grid[1]` and `grid[2]`. `fill` evaluates its argument once. Use `Array.from({length: 3}, () => [])`, whose mapping function runs per index and returns a distinct array each time.
  • Why does spread reject `{length: 3}` when Array.from accepts it?
    Spread is defined over iterables and requests the source's iterator; a plain object has none, so it throws a TypeError. `Array.from` first checks for an iterator and, finding none, falls back to the array-like path: it reads `length` and then indexes 0 through length - 1, yielding `undefined` for indexes that do not exist.

saying these in an interview costs you the question

  • Thinks Array(3) contains three undefined elements
  • Expects map to run over a holey array
  • Uses Array(n).fill(obj) and expects distinct objects
  • Believes spread works on any object with length
  • Confuses Array.of(3) with Array(3)

context