What if I told you everything you knew was a lie, what will happen if you learn some of the key features our beloved ECMAScript have published over the recent years, are actually dangerous performance traps, sugar coated in a slick looking one line callback functional code?
This story starts a few years ago, back in the naive days of ES5…
About the same time, a new environment grew — Node.js, it gave us the ability to have a smooth transition from front-end to back-end while truly redefining full stack development.
To test these questions, I tried comparing a few scenarios and drilled down to understand the results I got. I executed the following tests on Node.js v10.11.0 and in the Chrome browser, both on macOS.
1. Looping Over an Array
The first scenario which came to mind was summing an array of 10k items, this is a valid real-life solution I stumbled upon while trying to fetch a long table of items from the database and enhance it with the total sum, without having an additional query to the DB.
I compared the summing of random 10k items using for, for-of, while, forEach, and reduce. Running the tests 10,000 times returned the following results:
For Loop, average loop time: ~10 microseconds
For-Of, average loop time: ~110 microseconds
ForEach, average loop time: ~77 microseconds
While, average loop time: ~11 microseconds
Reduce, average loop time: ~113 microseconds
While googling how to sum an array, reduce was the best-offered solution but it’s the slowest. My go-to forEach wasn’t much better. Even the newest for-of (ES6) provides inferior performance. It turns out, the good old for loop (and also while) provides the best performance by far — 10x better!
2. Duplicating an Array
While this sounds like a less interesting scenario, this is the pillar of immutable functions, which doesn’t modify the input when generating an output.
Performance testing findings here again show the same interesting trend — when duplicating 10k arrays of 10k random items, it is faster to use the old school solutions. Again the trendiest ES6 spread operation `[…arr]` and Array from `Array.from(arr)` plus the ES5 map `arr.map(x => x)` are inferior to the veteran slice `arr.slice()` and concatenate `.concat(arr)`.
Duplicate using Slice, average: ~367 microseconds
Duplicate using Map, average: ~469 microseconds
Duplicate using Spread, average: ~512 microseconds
Duplicate using Conct, average: ~366 microseconds
Duplicate using Array From, average: ~1,436 microseconds
Duplicate manually, average: ~412 microseconds
3. Iterating Objects
Another frequent scenario is iterating over objects, this is mainly necessary when we try to traverse JSON’s and objects, and while not looking for a specific key value. Again there are the veteran solutions like the for-in `for(let key in obj)`, or the later `Object.keys(obj)` (presented in es6) and `Object.entries(obj)` (from ES8) which returns both key and value.
Performance analysis of 10k objects iterations, each of which contains 1,000 random keys and values, using the above methods, reveals the following.
Object iterate For-In, average: ~240 microseconds
Object iterate Keys For Each, average: ~294 microseconds
Object iterate Entries For-Of, average: ~535 microseconds
The cause is the creating of the enumerable array of values in the two later solutions, instead of traversing the object directly without the keys array. But the bottom line result is still causing concerns.
My conclusion is clear — if blazing fast performance is key for your application, or if your servers require to handle some load — using the coolest, more readable, cleaner options will blow a major punch to your application performance — which can get up to 10 times slower!
Next time, before blindly adopting the slickest new trends, make sure they also align with your requirements — for a small application, writing fast and a more readable code is perfect — but for stressed servers and huge client-side applications, this might not be the best practice.