JS Tip of the Day: Flattening Arrays

Flattening Arrays
Version: ES2019
Level: Beginner

Sometimes when you have an array of arrays, you may want to condense them into a single array, putting all of the values of the inner arrays into the single outer array. You can do that easily with the Array flat() method. flat() is called on any array and will return a new array that is a flattened version of the original.

let nested = [[1,2],[3],[4,5]];
let flattened = nested.flat();
console.log(flattened); // [1,2,3,4,5]

flat() also accepts a depth argument which lets you specify how many nested arrays should be flattened. By default this is 1.

let nested = [1,[2,[3,[4,[5]]]]];
let flatOne = nested.flat(); // [1,2,[3,[4,[5]]]]
console.log(flatOne);
let flatTwo = nested.flat(2); // [1,2,3,[4,[5]]]
console.log(flatTwo);
let flatFour = nested.flat(4); // [1,2,3,[4,[5]]]
console.log(flatFour); // [1,2,3,4,5]

Note that flat() only works with other arrays. It does not flatten iterables or array-likes in the original array.

One thing flat() can be useful for is converting a multi-dimensional array into a single dimension array to make it easier go through all the elements without having to worry about nested loops.

let ticTacToeBoard = [
    ['X', 'O', ' '],
    ['O', 'X', ' '],
    ['X', 'O', 'X'],
];

let allMarks = ticTacToeBoard.flat();
console.log(allMarks); // [X, O,  , O, X,  , X, O, X]

let numXs = allMarks.filter(mark => mark === 'X').length;
console.log(numXs); // 4

There’s also a version of map() with built-ing flattening called flatMap(). This would be similar to calling both flat() and map() but does them as a single operation. We’ll look at an example that uses this in a future tip.

More info:

Interesting article but I have a few questions. First, in your ticTacToeBoard example, will the original array reflect any changes that are made to the flattened array? Second I assume this it just a one way operation in that once the array is flattened there is no way to go back to the original array. That is correct right?

First, in your ticTacToeBoard example, will the original array reflect any changes that are made to the flattened array?

Not changes to the array itself. The array created is a brand new array, but its also a shallow copy. So while the array and its immediate elements are “copies”, anything deeper such as objects in the arrays are not copied. For ticTacToeBoard, because its just a collection of strings, changes to the flattened array would not affect the original. But if you had an array of objects that you flattened, changes to the objects in the flattened array would be reflected in the original array because they’re all the same objects.

let array2d = [
  [ { value: 'X' }, { value: 'O' } ],
  [ { value: 'O' }, { value: 'X' } ],
];
let arrayFlat = array2d.flat();
arrayFlat[0].value = 'O';
console.log(array2d);
/* [
  [ { value: 'O' }, { value: 'O' } ],
  [ { value: 'O' }, { value: 'X' } ],
] */

If you have simple values in a structure of nested arrays, having them wrapped in values like this could be a way to make them editable in flattened versions of that structure.

Second I assume this it just a one way operation in that once the array is flattened there is no way to go back to the original array. That is correct right?

That’s right. You could write something up yourself to do this, but there’s no built-in unflatten() or something similar that handles this automatically. If you wanted to convert the flattened ticTacToeBoard back to its original, you could do something like…

let ticTacToeBoard = [
    ['X', 'O', ' '],
    ['O', 'X', ' '],
    ['X', 'O', 'X'],
];

let allMarks = ticTacToeBoard.flat();
allMarks[4] = 'O';
function toArray2d (array, colCount) {
    let array2d = [];
    for (let i = 0; i < array.length; i += colCount) {
        array2d.push(array.slice(i, i + colCount));
    }
    return array2d;
}

ticTacToeBoard = toArray2d(allMarks, 3);
console.log(ticTacToeBoard);
/* [
    ['X', 'O', ' '],
    ['O', 'O', ' '],
    ['X', 'O', 'X'],
] */