Flattening Arrays in JavaScript with flat() and flatMap()

March 9, 2019

As of ES2019, there are two new methods on arrays: Array.prototype.flat() and Array.prototype.flatMap().

The Array.prototype.flat() Method

The flat() method can flatten a multidimensional array. For example:

[[1, 2], [3, 4, 5], [6]].flat();
// [1, 2, 3, 4, 5, 6]

By default, flat() will only go one level deep. You can flatten more than 1 level deep by passing a depth argument to flat():

[[1, 2], [3, [4, 5]], [6]].flat(2);
// [1, 2, 3, 4, 5, 6]

Here, the depth of this array is 2 since the array at index 1 contains the array [4, 5].

The Array.prototype.flatMap() Method

The flatMap() method is like calling map() followed by calling flat(1). For example:

['Hello', 'World'].flatMap(word => word.split(''));
// ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']

If we were to first call map on this array, this is what we’d get:

['Hello', 'World'].map(word => word.split(''));
// [['H', 'e', 'l', 'l', 'o'], ['W', 'o', 'r', 'l', 'd']]

Then, if we call flat(1) on this resulting array, we’d get the same result as when we called flatMap():

[['H', 'e', 'l', 'l', 'o'], ['W', 'o', 'r', 'l', 'd']].flat(1);
// ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']

So, flatMap() is like calling map followed by flat(1).

Writing Your Own flatMap() Function

Prior to the addition of flatMap() to JavaScript, I had a flatMap() utility function in one of my projects. The following was my implementation:

function flatMap(array, callback) {
  return array.reduce((accumulator, item) => {
    return accumulator.concat(callback(item));
  }, []);
}

And you could use it as such:

flatMap(['Hello', 'World'], word => word.split(''));
// ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']

You could also use flatMap() in Lodash, which I imagine is more robust and faster.

A Practical Example of flatMap()

One situation I have found flatMap() to be useful is when dealing with time series data. For example, imagine you get the following JSON from an API:

[
  {
    "timestamp": "2019-01-15",
    "purchases": [
      {"product": "Product 1", "total": 20, "time": "09:00"},
      {"product": "Product 1", "total": 60, "time": "12:00"}
    ]
  },
  {
    "timestamp": "2019-01-16",
    "purchases": [
      {"product": "Product 1", "total": 40, "time": "10:00"},
      {"product": "Product 2", "total": 30, "time": "11:00"}
    ]
  },
  {
    "timestamp": "2019-01-17",
    "purchases": [
      {"product": "Product 1", "total": 10, "time": "14:00"},
      {"product": "Product 2", "total": 20, "time": "17:00"}
    ]
  }
]

If I wanted to plot this data on a chart, I might need to collect the total for each respective product into a single array, depending on the charting library. I could do that with flatMap()!

let product1Sales = json.flatMap(({ purchases }) => {
  return purchases
    .filter(sale => sale.product === 'Product 1')
    .map(sale => sale.total);
});

// [20, 60, 40, 10]

Disclaimer: Any viewpoints and opinions expressed in this article are those of David Tang and do not reflect those of my employer or any of my colleagues.