To fill a JavaScript array with the same value, use Array(n).fill(value). To build a number sequence (a range), use Array.from({length: n}, (_, i) => start + i). They look interchangeable, but they are not: fill puts the same value in every slot, which is exactly what you want for a primitive like 0 and exactly what bites you when the value is an object. The sequence builder runs a function per index, so each slot is computed fresh. Pick based on whether every element is identical or each one is derived from its position.
Array(5).fill(0); // [0, 0, 0, 0, 0]
Array.from({length: 5}, (_, i) => i); // [0, 1, 2, 3, 4]
Array.from({length: 5}, (_, i) => i + 1); // [1, 2, 3, 4, 5]Fill an array with one constant value
Array(n) gives you an array of length n with no actual elements (the slots are empty, or "holes"). On its own that is nearly useless: map and forEach skip holes, so you cannot transform it. fill materializes every slot:
const zeros = Array(4).fill(0); // [0, 0, 0, 0]
const blanks = Array(3).fill(""); // ["", "", ""]
const flags = Array(3).fill(true); // [true, true, true]For primitives (numbers, strings, booleans, null) this is perfect. Each slot holds an independent copy of the value, because primitives are copied by value. That breaks the moment the fill value is an object.
The fill reference trap (the thing that bites everyone)
This is the one to internalize. fill does not run a function per slot, it takes a single value and writes that same value into every position. When the value is an object or an array, you get n references to one shared object, not n distinct objects:
const rows = Array(3).fill([]);
rows[0].push("a");
console.log(rows); // [["a"], ["a"], ["a"]] -- all three changedThere is one array in memory; all three slots point at it. Pushing through rows[0] mutates the array the other two also reference. The same happens with Array(3).fill({}): three pointers, one object.
When you want distinct objects, you need something that runs per slot. Array.from with a map callback does exactly that, calling the function once for each index:
const rows = Array.from({length: 3}, () => []);
rows[0].push("a");
console.log(rows); // [["a"], [], []] -- only the first changedEach invocation of the arrow returns a brand-new array literal, so the three slots hold three independent arrays. Same pattern for objects: Array.from({length: 3}, () => ({})) (the parentheses keep the parser from reading the braces as a function body). Reach for this whenever the per-slot value is anything other than a primitive.
Build a number sequence (a range)
The clean way to generate [start, start+1, ..., start+n-1] is Array.from with the index argument. The map callback receives the value (always undefined here, so I name it _) and the index:
// 0..9
Array.from({length: 10}, (_, i) => i);
// a range from `start` (inclusive) of length `n`
const range = (start, n) => Array.from({length: n}, (_, i) => start + i);
range(5, 4); // [5, 6, 7, 8]
// with a step
const step = (start, n, by) => Array.from({length: n}, (_, i) => start + i * by);
step(0, 5, 10); // [0, 10, 20, 30, 40]You will also see the older Array(n).fill().map((_, i) => i) form. It works, but it is doing two passes: fill() materializes the holes (so map will visit them), then map builds the result. Array.from({length: n}, mapper) does it in one pass with no intermediate array, and reads more directly as "make an array of this length, here is each element." I use Array.from for sequences and keep fill for the constant case.
Partial fill: fill(value, start, end)
fill takes optional start and end indices (end is exclusive, same convention as slice), so you can overwrite a slice of an existing array in place. It mutates the array and returns it:
const a = [1, 2, 3, 4, 5];
a.fill(0, 1, 3); // [1, 0, 0, 4, 5] -- indices 1 and 2
a.fill(9, 3); // [1, 0, 0, 9, 9] -- index 3 to the end
a.fill(7); // [7, 7, 7, 7, 7] -- whole arrayNegative indices count from the end (a.fill(0, -2) fills the last two slots). Because fill mutates and returns the same array, do not expect a copy: const b = a.fill(0) leaves b and a pointing at the one mutated array.
FAQ
See also
- Array.from and the spread operator: converting NodeLists, Sets, and array-likes to real arrays, and why the map callback is what makes
Array.fromworth keeping. - Modern JavaScript array methods: which method to reach for in 2026, including the broader mutate-in-place versus copy distinction that the
filltrap is one case of. - Get a random item from an array: once you have built or filled an array, pulling a random element (and shuffling one correctly).
Sources
Authoritative references this article was fact-checked against.
- Array.prototype.fill() (MDN)developer.mozilla.org
- Array.from() (MDN)developer.mozilla.org





