visit
In programming, just as in life, we are constantly making decisions about what problems we are willing to deal with. When architecting software, we accept trade-offs with each decision.
A language built with specific initial goals will always carry certain architectural and optimization biases that may not align with newer, shinier features.
To move beyond assumptions and truly understand performance trade-offs, we must test our theories in practice. I’ve used a structured performance testing framework within GitHub Codespaces, running Node.js v22, to measure execution time and memory usage at varying scales. Testing patterns at 10, 100, 1,000, 10,000, and even 100,000 iterations helps reveal when certain optimizations kick in and how different environments can influence results.
As you dive into these examples, remember that performance isn’t just a matter of syntax. The underlying system, operating system version, and hardware configuration can all impact performance. The goal is to discover general principles by testing, not assuming. After all, in a language like JavaScript (insert any other language here), the prettiest code isn’t always the fastest.
Performance results are highly sensitive to context—such as the underlying operating system and the size of data flowing through the code. So, take the following results with a grain of salt; the benchmarks were run using a small performance testing framework I created last week.
To be 100% sure of your performance, you need to set aside general assumptions and conduct your own tests—you may be surprised!
If execution speed is a primary concern, optimize for that. However, in the real world, performance might not be your only consideration. It's important to find a solution that strikes the right balance. Therefore, always make a conscious assessment and understand first what should be optimized. For more on this topic, see To optimize or not to optimize.
As engineers, we may have a bias toward a "favorite language"—perhaps the first one we used or the one we employed to build an interesting project. In the end, if you truly care about performance, you might as well find the best tool (or programming language) for a particular job—which may not be JavaScript/TypeScript. The same goes for every other language.
+
Operator)
Performance tests showed template literals were executed faster, on average in 0.0007ms
vs regular string concatenation 0.0011ms
for 100 iterations and template literals remained faster when tested against 100k iterations, averaging 0.0002ms
vs 0.0003ms
.
slice()
vs Spread Operator)When comparing the spread operator to the slice()
method, I found that (for smaller arrays at least), slice()
outperformed the spread operator. The average execution of slice()
was 0.0013ms
which was shorter than spead operator, averaging 0.0018ms
. However, when the performance test was scaled to 100k, iterations discovered that it performed roughly the same.
for
vs forEach
)Traditional for
loops are generally faster due to fewer function calls and memory overhead compared to forEach()
.
For 100 iterations, performance tests demonstrated for
loops outperformed forEach()
by executing 8% faster. This result was surprising, as I was personally expecting a much bigger gap. Then again, it could be down to the amount of data that was used for iteration, or it could be over time forEach()
have been optimized.
The performance results observed for these tests showed counterintuitive results! I was expecting the dot notation to have the upper hand. However, in a simple performance test, where not much else is going on, except these two code snippets, we found that for 100 iterations, destructuring was 57% faster.
map()
vs for
Loopfor
loops can outperform map()
since there's less overhead in function calls, especially in performance-critical sections.
Surprisingly, using a small data array (~100 items of varying data types), found that it was virtually a draw! Regardless of whether it was 100 iterations or 100k iterations. The actual difference was too small be relevant, but if for
loop beat map
.
In relatively, if I had to declare a winner, it would be map()
since the difference found was so negligible that I’d much better the clear more succinct code.
Object.assign()
vs Spread Operator)Spread syntax can be faster and more concise when cloning objects compared to Object.assign()
.
For 100 iterations, the spread operator was 35% faster than using Object.assign()
. This completely challenges and proves the exception to the rule. Apparently, the “pretty” syntax was faster!
For 100k iterations, similar to other tests, performance seemed to improve for both approaches, but the spread operator seemed to be the superior choice, being 40% faster.
||
vs Default Syntax)
For 100 iterations, using ||
operator seemed 6% faster. For 100k iterations, however, the situation reverted, and default parameters were 33% faster!
I conclude there isn’t a clear winner for this one. Ultimately, the better performance test result is one run against the actual code you want to test. I would not take the results here and assume they will be the same for different codes, as things occurring in real production code may affect how Node executes code.
indexOf
vs includes
)includes()
is faster and more readable for checking existence in arrays. By how much? includes()
was 50% faster than indexOf()
for 100 iterations test. Under the 100k iterations test, the difference in execution speed was negligible.
hasOwnProperty
vs in
Operator)The in
operator is faster and simpler in many cases for object key lookups.
Both approaches clocked in about 0.0012ms
for 100 iterations and 0.0002ms
for 100k.
It’s a draw! However, in such cases, I might prefer the succinctness that comes from the in
operator.
filter()
vs. Loop and Condition)for
loops can often be faster for filtering large arrays as they avoid function calls and temporary arrays.
I discovered in 100 iterations the performance test, filter()
happened to be about 11% faster than the loop and condition approach.
On 100k iterations, surprisingly, the situation was reversed; loop and condition outperformed filter()
by completing executions 33% faster.
Math.pow()
vs Exponentiation OperatorThe exponentiation operator (**
) is faster than Math.pow()
for small calculations, or so I thought.
Math.pow()
was approximately 11% faster that **
on average for 100 iterations.
For 100k iterations, however, **
was 33% faster.
concat()
vs Spread Operator)
Tests showed concat()
was 25% faster than the spread operator, with 100 iterations. With 100k, however, the difference seemed negligible.
reduce()
vs for
While reduce()
is clean and readable, for
loops can be faster for basic summing or accumulating values, is what I thought.
I am not giving out the answer to this one 😄. Which one do you think will be faster?
&&
vs if
Statements
It is a draw.
flat()
vs Custom Loop)Manual flattening can be faster for shallow arrays compared to flat()
, which can handle deep nesting but with overhead.
The verbose custom loop was approximately 38% faster on average during 100 iteration tests and 44% faster during 100k iteration tests!
When it comes to performance, it seems like a custom loop wins by a landslide.
However, if you would have to compare how a custom loop performance flattens N depths, I suspected it might have diminishing returns in optimization when compared to using flat()
. If this is your case, it might be worth putting it to the test.
class
keyword was introduced to JavaScript in ES6
, which was a while ago. It turns out that declaring classes and methods class
is less performant. Using prototypes yields fast execution times, being 20% faster than using class
. For 100k iterations, while prototypes are still faster, the gap is much smaller and just about 8% faster.
for
vs. Custom Iterator)
Using custom iterators was 12% faster on average for 100 iterations of performance tests. However, during the 100k iterations test, it turns out optimizations that occur at that scale favor simply for loops better, being 33% faster than the custom iterators.
That’s a wrap!
If you leave parroting that X is faster than Y —well, don’t! You’ve missed the point.
Seriously, Test. Test. Test.