Carl Willimott - Tech Blog: Using flatMap in place of map and reduce

Using flatMap in place of map and reduce

November 22, 2022

4 min read

Back

One of the most common things you find yourself doing in javascript, is iterating over collections of data. If you are experienced working as a front-end developer, this could be dealing with API responses or something which typically has one or more of something such as select options. For a back-end developer, this could be anything from creating APIs to processing large amounts of data in an ETL pipeline (and anything in between).

As you are probably aware, javascript arrays make it simple to chain multiple methods one after another - so you can transform your data as required. When doing this, a new object is created so you don't have to mutate the original variable.

In this first example we want to take the array of items, remove the items where active is set to false and add a new property called timestamp which is the current datetime.

const items = [
    {
        id: 1,
        active: true
    },
    {
        id: 2,
        active: true
    },
    {
        id: 3,
        active: false
    },
    {
        id: 4,
        active: false
    },
    {
        id: 5,
        active: true
    },
];

This is quite trivial, and can be done as follows.

items.filter(item => item.active).map(item => ({ ...item, timestamp: Date.now() }));

You would get something like the following.

[
    {
        active: true,
        id: 1,
        timestamp: 1669107835817
    },
    {
        active: true,
        id: 2,
        timestamp: 1669107835817
    },
    {
        active: true,
        id: 5,
        timestamp: 1669107835817
    }
]

Now then, this is probably fine for most use cases, but what about if wanted to only iterate over the array once? Well, you could do something like the following with the reduce method.

items.reduce((acc, curr) => {
     if (curr.active) {
         acc.push({ ...curr, timestamp: Date.now() });
     }
     return acc;
}, []);

Whilst this works, there are a couple of ways to write this in a more succinct / functional way. The first makes use of the spread operator and the second (which is more readable / intuitive in my opinion) makes use of concat.

items.reduce((acc, curr) => [...acc, ...curr.active ? [{ ...curr, timestamp: Date.now() }] : []], []);

items.reduce((acc, curr) => curr.active ? acc.concat({ ...curr, timestamp: Date.now() }) : acc, []);

Now then, whilst you can totally stop there - it's completely possible to do something similar with the flatMap method, but before we go into that, you might want to see a bit more about how else you can use it.

What is flatMap?

Similar to how the map method works, we can use this method to map over an array and apply a function to each item. At this point we can also return a new / modified item if we prefer. The main difference here is that, we also flatten the array.

The classic example taken from the MDN docs goes something like this.

const items = [1, 2, [3], [4, 5], 6, []];

items.flatMap(item => item);

// This gives the output of: Array [1, 2, 3, 4, 5, 6]

Whilst this is great and definitely has its uses, we can take full advantage of it for our map and filter combo.

items.flatMap(item => item.active ? ({ ...item, timestamp: Date.now() }) : []);

By effectively returning an empty array [] in the negative branch of the ternary operator, the flatMap method is able to completely filter this value out and give the same output as each of the previous examples - cool, huh?

Conclusion

Okay, so if you got this far you are probably thinking that you should always use this from now on? Well as always, the answer depends. Using the flatMap is more than likely going to lead to a performance hit than using a reduce method on a comparable input array, but I think that due to its simple nature, you should definitely consider using it if you have some guarantees on the size and structure of your data because in my opinion it's much simpler for users to understand than reduce. This is mostly because the callback function has the same call-signature as the map method.

Whether you use it or not is totally up to you, but if you want something easy to read and succinct, then this might be the way to go.

Useful links