Recently I was hit with a problem in performance with Javascript. I needed to find an intersection of two arrays. My brain immediately went towards the solution of nesting two for loops, then comparing each element inside of it:

const fruitInStoreA = ["apple", "banana", "blueberries", "strawberries"]
const fruitInStoreB = ["apple", "banana", "pineapple"]

const fruitInBothStores = []

for (const fruitA of fruitInStoreA) {
  for (const fruitB of fruitInStoreB) {
    if (fruitA === fruitB) {
      fruitInBothStores.push(fruitA)
    }
  }
}

console.log(fruitInBothStores.join(", "))

The above code prints the string apple, banana. It works as it should, so where's the problem?

The problem comes when the lists get larger. For example, a news website can have millions of articles. An online store can have millions of orders, etc. In the big O notation, the complexity is O(n^2). In this case, n represents the number of elements. Thus, the number of elements will square the time to process it!

For example, if one element takes 2 seconds to process, ten elements will need 100 seconds!

So how would we tackle such a problem?

Enter the world of objects

The most effective way of removing the time complexity is by replacing using an object. We can map the first array to an object, setting the array element as its key and set the value to true.

How to do this:

const fruitInStoreA = ["apple", "banana", "blueberries", "strawberries"]
const fruitInStoreB = ["apple", "banana", "pineapple"]

const fruitInBothStores = []

const mapOfFruitInA = {}

for (const fruitA of fruitInStoreA) {
  mapOfFruitInA[fruitA] = true
}

for (const fruitB of fruitInStoreB) {
  if (mapOfFruitInA[fruitB]) {
    fruitInBothStores.push(fruitA)
  }
}

console.log(fruitInBothStores.join(", "))

The most significant change from our previous solution is the addition of mapOfFruitInA, and the split for statements.

To explain this in more detail, we map all the values from the fruitInStoreA to the object keys in mapOfFruitInA and then setting the object values as true. Doing this allows us to read if a value is present without needing to loop through the array.

We're still using two for loops for it, but now they're not nested.

In big o terms, this represents an O(n) scenario since the time complexity is linear with the number of elements, which is much better than square. Meaning that if one element needs 1 second to process it, then ten elements will need 10 seconds, not 100!

(Note: To be precise, this is an O(n+m) scenario since the number of elements in fruitInStoreA and fruitInStoreB is different, but I changed it for the sake of simplicity!)

Pretty good improvement, right?

Conclusion

This technique is convenient to keep in your toolbox. It appeared helpful for me while working as an engineer and while interviewing.

I hope this will speed up your algorithms and remove unnecessary bottlenecks!

Thanks for reading 👋

Removing nested loops in Javascript