TechEarl

Fill an Array With Values or a Number Sequence in JavaScript

Fill a JavaScript array with a constant value, build a number sequence, or do a partial fill: when to use Array(n).fill() versus Array.from({length: n}, mapper), and the reference trap that bites everyone.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
Fill a JavaScript array with a constant value, generate a number range with Array.from, do a partial fill, and avoid the shared-reference trap.

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.

javascript
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:

javascript
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:

javascript
const rows = Array(3).fill([]);
rows[0].push("a");
console.log(rows);  // [["a"], ["a"], ["a"]]  -- all three changed

There 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:

javascript
const rows = Array.from({length: 3}, () => []);
rows[0].push("a");
console.log(rows);  // [["a"], [], []]  -- only the first changed

Each 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:

javascript
// 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:

javascript
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 array

Negative 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

Sources

Authoritative references this article was fact-checked against.

TagsJavaScriptarraysArray.fillArray.fromfill arraynumber range

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts