Optimizing Loop In JavaScript
SR Sajjad
Posted on April 16, 2020
If you've been writing code for a while you might've developed a level of empathy for your machine. "It shouldn't work much while running my code". Not speaking of hard core algorithm optimization. But yah, it surely feels bad when there's a loop inside another loop.
While writing JavaScript, there are many places we could optimize our code to run faster.
Like -
- take the hot code out of main thread
- make async calls parallel with
Promise.all
- debounce or throttle certain functions
- use CSS properties that will trigger less rendering cycles
...and such points go on.
One most obvious thing among them is Loop. Loops are scary.
But maybe in most cases, the loop isn't the reason for bad performance but you might want to avoid unnecessary iterations. Hence the blog post people !
The target is actually any kind of repetition like - loop, recursion or whatever
In JavaScript there are many APIs to write loop. These days we mostly use map
, reduce
, filter
, forEach
. It feels really good to write them. Because they enforce functional approach and the code management is quite good as well. Some people hate reduce
though ;) .
Let's jump to the points - how we can write better Loops ?
The Magic Words - break
, continue
and return
Let's look at this example -
for(let i = 0; i < arr.length; i++){
// we have got our answer
break
// we don't need to go further in this iteration
continue
// heavy calculation here
}
Do you see what's happening here ? Now in forEach
, map
, filter
- they don't stop. They are going to run through all iterations until the last index
. Break doesn't work.
So in such cases we should pick for loop
instead of trying to be cool. While the classic for loop
is perfectly fine, you might want to use a better looking API - for of
.
for (let val of arr){
// we have got our answer
break
// we don't need to go further in this iteration
continue
// heavy calculation here
}
Now the gotcha is - what if we need index
inside this loop ? In for of
there's no direct index
support. But there's always a hack for almost everything.
for (let [index, val] of Object.entries(arr)){
}
If the loop is inside a function, and we use return
inside that loop, the whole function is going to return.
function doSomething(){
for(let val of arr){
// mission accomplished
return
// some heavy calculation going on here
}
}
This can't be done in forEach
, map
or some other array looping functional method. Because they have their own return
.
You Might Not Need Iteration
Let's look at another example -
let playerInfo = [
{
name: 'Messi',
club: 'Barcelona'
},
{
name: 'Ronaldo',
club: 'Juventus'
},
{
name: 'Neymar',
club: 'PSG'
}
]
// here we want to find Neymar's club from this array
console.log(playerInfo.find(player => player.name === 'Neymar').club)
For this we need to loop over each element and see if it's Neymar and then get the club's value.
Sometimes a hashing/ dictionary approach would be better. Because then we don't need to iterate again and again. Just access the value directly.
const playerInfo = {
Messi: 'Barcelona',
Ronaldo: 'Juventus',
Neymar: 'PSG'
}
console.log(playerInfo.Neymar)
Maybe it's not the best example, but I am pretty sure you'd find better use case for this approach.
In some cases, this kind of object
approach can save you from O(N^2)
complexity.
// let's find out if there's any repetition in this array
let arr = [1, 2, 3, 1] // 1 appears twice, so there's repetition
// loop on every item
// on another inner loop check -
// if this item has appeared in any other index
// so that would be O(N^2) solution
// Or,
// hash the value on one loop
let obj = {}
arr.forEach((v,i) => obj[v] ? obj[v]++ : obj[v] = 1)
// and on another loop check if some key has more than 1 value
// that would be of O(N+N) complexity and that's better
For some cases, you might consider a math equation instead of loop. Like - find out the summation of an explicit sequence.
let arr = [1, 2, 3, 4, 5]
// which is better in this case ?
// this ?
let total = arr.reduce((currentVal, reducedVal) => currentVal + reducedVal , 0)
// or this ?
let n = 5 // last element - arr[arr.length - 1]
let sum = (n * (n+1)) / 2
// another dumb example
// which is better here ?
let arr = [2, 2, 2, 2, 2, 2]
// this ?
let total = eval(arr.join('+')) // eval ? really ??
// or this ?
let sum = 2 * arr.length
Use the Correct Array Method for Particular Scenario
There are varieties of built in array methods available in JavaScript. Some are similar but each one has it's own purpose. It's better to think twice before applying map
or filter
for all use cases.
For example - find
vs filter
find
is a better suit if we are looking for only one item. And find
stops iterating after the desired value is found. filter
would iterate until the last index as it's looking for all the matches.
There are other same cases.
Memoization
Sometimes there could be same function calls with same parameters, in those cases we can save the value on first execution. Instead of running the function again, we could just use that saved value. This process is called memoization.
Just to give a rough idea - a silly example - look for better examples on the internet.
let cache = {}
function plus(x){
// there might be heavy calculation here
console.log('i am here') // LOL
return x + 2
}
function memoizedPlus(a){
if(cache[a]){
return cache[a]
}
else{
cache[a] = plus(a)
return cache[a]
}
}
// run this code in your console and see what happens
console.log(memoizedPlus(5))
console.log(memoizedPlus(1))
console.log(memoizedPlus(5))
console.log(memoizedPlus(3))
console.log(memoizedPlus(3))
I had a plan to talk about handling async operation inside loop. Maybe in another article. So for now that's all folks ! Stay safe and have fun.
Posted on April 16, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.