JAVASCRIPT / ARRAYS
Reversing, filling, and flat array shapes
Reverse and fill arrays in place, build fixed-size arrays without shared references, and flatten nested data with flat and flatMap.
What you will learn
- reverse() and fill() mutate the array and return it, not a copy
- Pick a flat() depth on purpose: flat() is one level, flat(Infinity) is all of them
- Use flatMap when the callback returns arrays; return [] to drop an element
- new Array(n).fill([]) shares one array; Array.from({length:n},()=>[]) does not
Understanding Reversing, filling, and flat array shapes
reverse() and fill() are in-place rewriters: they change the array you called them on and then return that very same array, so const b = a.reverse() leaves you with two names for one object. reverse() swaps index i with index length - 1 - i until the two ends meet in the middle, which is why it needs no extra memory but destroys the previous order permanently. fill(value, start, end) writes value into every index from start up to but not including end, and negative bounds count back from the end, so arr.fill(0, -2) clears just the last two slots.
flat() is about depth, not length. With no argument it splices one level of nested arrays into the outer array, flat(2) goes two levels, and flat(Infinity) keeps going until nothing nested is left. Only real arrays are unwrapped, so strings, Sets, and array-likes stay as single elements, and the source is never touched because flat() returns a new array. flatMap(fn) is map(fn).flat(1) fused into a single pass, which is why a callback returning [] deletes an item and one returning two values expands it, while anything nested deeper than one level survives.
Keeping the two families apart is the whole lesson: reverse() and fill() mutate and hand back the receiver, flat() and flatMap() allocate and hand back something new. That is why reversing an array you have already passed to another function is a bug waiting to happen, and why ES2023 added toReversed() as the copying twin of reverse(). fill() has no copying twin, and it carries a second trap: the value expression is evaluated once, so new Array(3).fill([]) stores the identical array object in all three slots, whereas Array.from({ length: 3 }, () => []) runs the function per index and produces three distinct arrays.
const letters = ['a', 'b', 'c', 'd'];
const same = letters.reverse();
console.log(letters, same === letters);
const row = new Array(4).fill(0);
console.log(row);
console.log(row.fill(9, 1, 3));
const nested = [1, [2, [3, [4]]]];
console.log(nested.flat());
console.log(nested.flat(Infinity));
console.log(nested.length, nested.flat(Infinity).length);reverse() and fill() rewrite the array in place and return the same reference, while flat() and flatMap() leave the original alone and return a new array.
Worked examples
fill shares one object, Array.from does not
Shows why filling with [] or {} gives every slot the same object, and the mapping-function alternative.
const grid = new Array(3).fill([]);
grid[0].push('x');
console.log(grid);
console.log(grid[0] === grid[2]);
const safe = Array.from({ length: 3 }, () => []);
safe[0].push('x');
console.log(safe);
console.log(safe[0] === safe[2]);Example explained
Line 1new Array(3).fill([]) evaluates the [] literal once, so one array object is written into all three slots.
Line 2grid[0].push('x') mutates the object that grid[1] and grid[2] also point at, which is why all three rows show 'x'.
Line 3grid[0] === grid[2] being true is the proof that only a single array was ever created.
Line 4Array.from({ length: 3 }, () => []) calls the arrow once per index, so the slots are independent and safe[0] === safe[2] is false.
flatMap to expand, drop, and keep pairs
Demonstrates that flatMap concatenates callback results and flattens exactly one level.
const orders = [
{ id: 1, items: ['pen', 'ink'] },
{ id: 2, items: [] },
{ id: 3, items: ['pad'] }
];
console.log(orders.flatMap(o => o.items));
console.log([1, 2, 3, 4, 5].flatMap(n => (n % 2 === 0 ? [] : [n, n])));
console.log([1, 2].flatMap(n => [[n, -n]]));Example explained
Line 1flatMap(o => o.items) concatenates each items array, so order 2 with an empty items list contributes nothing at all.
Line 2Returning [] for even numbers removes them while returning [n, n] for odd ones turns one input into two outputs.
Line 3The last call returns an array containing an array, and since flatMap strips only one level the [n, -n] pairs stay intact.
Reversing without wrecking the original
Compares copy-then-reverse, toReversed, and the aliasing bug you get from reverse alone.
const scores = [10, 20, 30];
const desc = scores.slice().reverse();
console.log(scores, desc);
const stack = ['a', 'b', 'c'];
console.log(stack.toReversed(), stack);
const oops = ['a', 'b', 'c'];
const alias = oops.reverse();
alias.push('z');
console.log(oops);Example explained
Line 1scores.slice() copies first, so reverse() rewrites the copy and scores keeps its ascending order.
Line 2toReversed() builds a new array directly, which is why stack still prints in its original order right after it.
Line 3alias is not a copy of oops but the same array, so the push lands in oops too and it ends up with four elements.
Important notes
The three methods disagree about empty slots: flat() drops holes, fill() writes the value into them, and reverse() moves them while keeping them holes.
toReversed(), toSorted() and with() are ES2023 additions; on older runtimes use slice().reverse() instead.
Common mistakes
Calling arr.reverse() to get 'a reversed version' and then reading arr again: reverse() returns the same array, so the original order is already gone and later code silently reads reversed data.
Building grid rows with new Array(3).fill([]) or fill({}): all slots hold one object, so writing to row 0 appears to change every row at once.
Assuming flat() flattens everything: [1, [2, [3]]].flat() still contains [3], and that leftover array usually resurfaces later as NaN or a string like '3' in arithmetic and comparisons.
Try it yourself
Change, predict, then run
In a browser console, start from const nested = [[1, 2], [3, [4, 5]], []] and log a fully flat array of its five numbers without changing nested. Then build a three-slot array whose slots are independent empty arrays, push one number into the first slot, and log it to confirm the other two are still empty.
Open the JavaScript workspaceCheck your understanding
After const a = [1, 2, 3]; const b = a.reverse(); b.push(4); what does a hold?
- [1, 2, 3] because reverse() returns a reversed copy and leaves a untouched
- [3, 2, 1] because a was reversed, but push() only grew the separate array b
- [3, 2, 1, 4] because b is the same array as a, so both operations landed on it
- [1, 2, 3, 4] because push() mutates a while reverse() does not
Show answer
reverse() rewrites the array it is called on and returns that same reference, so b and a name one array: it is reversed in place and then gets 4 appended. Option 2 in the list is tempting because flat(), flatMap() and toReversed() really do hand back new arrays, but reverse() is not one of those copying methods.