Can you make a one-liner change to the below to ~100x its performance? const ans = hugeArray .filter((item) => item.isEven) .map((item) => calculateSumOfDigits(item.id)) .some((item) => { return item > 20; }); If you have the answer feel free to skip the rest of the article. If not, then the below write-up might help you. Array methods: declarative and descriptive Array methods like Array.protoype.map(), Array.protoype.filter() were introduced more than 10 years back in JavaScript and have found great adoption. In this last decade, these friendly helpers have slowly and surely become integral to our JS codebases. Not just because they improve code readability, but also because they align with functional programming paradigm where the guideline is to be more declarative than iterative. The above code can be easily written with a for loop. Some folks might even point out that a for loop would be more performant. But given how well code these methods have integrated into our daily life as JS developers, it would not be surprising to find. Even if not in practicality, it is crucial to know how the above is non-performant when compared to its alternative. What's the problem? Methods like Array.protoype.map(), Array.protoype.filter() etc. return new arrays without modifying the existing ones. That is one of the biggest advantages they come with. It seems clear that in the above code snippet, we are creating 2 intermediary arrays. Given we are working with a huge array (with 100000 elements), this might not be ideal. Why do something that's not needed? That is precisely why a simple for loop would be much more efficient above. But is there a way we can still have declarative behavior, while not losing out on performance? Using iterables Converting the array to an iterator object and then iterating on it gives us a huge performance boost. const ans = hugeArray .values() .filter((item) => item.isEven) .map((item) => calculateSumOfDigits(item.id)) .some((item) => { return item > 20; }); Array.prototype.values() returns an iterator object that iterates the values of each item in the array. So instead of calling array methods like Array.protoype.map() and Array.protoype.filter(), we will call Iterator.protoype.map() and Iterator.protoype.filter(). The biggest difference between Array.protoype.filter() and Iterator.protoype.filter() is that the latter does not create an array at all. The difference is staggering both in terms of performance and memory usage. Below is a dry run for a 100000 records. Without .values() it takes 4ms, and with .values() it takes just 0.06ms. The memory footprint is also lower since we are not creating new arrays. The gap widens even more when the array has even more elements. The above Iterator methods are new addition to Javascript and are supported in recent versions of both Node and Browsers. For example, I ran the above on Node 22. This addition might also promote the adoption of generators. Generator Usage A Generator object (returned from generator functions) is just another type of iterator object. The key feature has always been the fact that the values yielded from a generator object are not evaluated until used. One of the major reasons for low adoption of generators was cited to be the lack of declarative helper methods like map(), filter() etc. Now, that these methods are available, it becomes easier to replace existing array implementations with generators. As seen above this reduces both an operations processing time and its memory footprint.

Apr 12, 2025 - 20:28
 0

Can you make a one-liner change to the below to ~100x its performance?

  const ans = hugeArray
    .filter((item) => item.isEven)
    .map((item) => calculateSumOfDigits(item.id))
    .some((item) => {
      return item > 20;
    });

If you have the answer feel free to skip the rest of the article. If not, then the below write-up might help you.

Array methods: declarative and descriptive

Array methods like Array.protoype.map(), Array.protoype.filter() were introduced more than 10 years back in JavaScript and have found great adoption. In this last decade, these friendly helpers have slowly and surely become integral to our JS codebases. Not just because they improve code readability, but also because they align with functional programming paradigm where the guideline is to be more declarative than iterative.

The above code can be easily written with a for loop. Some folks might even point out that a for loop would be more performant. But given how well code these methods have integrated into our daily life as JS developers, it would not be surprising to find. Even if not in practicality, it is crucial to know how the above is non-performant when compared to its alternative.

What's the problem?

Methods like Array.protoype.map(), Array.protoype.filter() etc. return new arrays without modifying the existing ones. That is one of the biggest advantages they come with. It seems clear that in the above code snippet, we are creating 2 intermediary arrays. Given we are working with a huge array (with 100000 elements), this might not be ideal. Why do something that's not needed? That is precisely why a simple for loop would be much more efficient above.

But is there a way we can still have declarative behavior, while not losing out on performance?

Using iterables

Converting the array to an iterator object and then iterating on it gives us a huge performance boost.

  const ans = hugeArray
    .values() 
    .filter((item) => item.isEven)
    .map((item) => calculateSumOfDigits(item.id))
    .some((item) => {
      return item > 20;
    });

Array.prototype.values() returns an iterator object that iterates the values of each item in the array.

So instead of calling array methods like Array.protoype.map() and Array.protoype.filter(), we will call Iterator.protoype.map() and Iterator.protoype.filter().

The biggest difference between Array.protoype.filter() and Iterator.protoype.filter() is that the latter does not create an array at all.

The difference is staggering both in terms of performance and memory usage. Below is a dry run for a 100000 records.

Image description

Without .values() it takes 4ms, and with .values() it takes just 0.06ms. The memory footprint is also lower since we are not creating new arrays. The gap widens even more when the array has even more elements.

The above Iterator methods are new addition to Javascript and are supported in recent versions of both Node and Browsers. For example, I ran the above on Node 22. This addition might also promote the adoption of generators.

Generator Usage

A Generator object (returned from generator functions) is just another type of iterator object. The key feature has always been the fact that the values yielded from a generator object are not evaluated until used. One of the major reasons for low adoption of generators was cited to be the lack of declarative helper methods like map(), filter() etc. Now, that these methods are available, it becomes easier to replace existing array implementations with generators. As seen above this reduces both an operations processing time and its memory footprint.