Skip to Content
Course content

139: Optimizing Loops and Large Data Processing

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you're dealing with a few hundred items in an array, you can write whatever code you want. You can chain five different array methods together, and the browser won't even blink. But once you hit 100,000 or a million records, the "elegant" way of writing JavaScript can actually become a liability. I've seen production apps freeze for seconds just because someone decided to be too "functional" with a massive dataset.

Let's build a small utility that processes a huge set of sensor readings. We want to filter out "noisy" data (readings below a certain threshold) and then calculate the total average of the remaining values.

The "Readable" but Expensive Chain

My first instinct is usually to go for readability. I'll start with a chain of .filter() and .reduce(). It looks clean, and it's easy for another dev to understand at a glance.

const readings = Array.from({ length: 1000000 }, () => Math.random() * 100);

console.time('Chain-Method');
const total = readings
  .filter(val => val > 10)
  .reduce((acc, val) => acc + val, 0);
console.timeEnd('Chain-Method');

Here is the problem: .filter() creates a brand new array. If my original array has a million elements and 900,000 pass the filter, JavaScript just allocated a massive amount of memory for a temporary array that exists for about half a second before the .reduce() finishes and the garbage collector has to come clean it up. That "stop-the-world" garbage collection is where the stuttering in your UI comes from.

Wait, why did I use for...in?

I'll be honest—sometimes I try to optimize too quickly and make a rookie mistake. I remember once trying to speed up a loop by switching to a for...in loop thinking it was a shorthand for indices. I did this in a similar project:

// DON'T DO THIS
for (let index in readings) {
  if (readings[index] > 10) {
    total += readings[index];
  }
}

I quickly realized my mistake when the performance actually dropped. for...in is designed for iterating over object properties, not array elements. It iterates over all enumerable properties, including ones inherited from the prototype chain, and it treats the index as a string, not a number. It's significantly slower than a standard loop. I scrapped that immediately.

Flattening the Loop for Maximum Throughput

To actually optimize this, we need to do everything in a single pass. We want to avoid creating any intermediate arrays and avoid any unnecessary function calls inside the loop (since calling a callback function a million times adds up).

The "boring" for loop is almost always the fastest way to process large data in JavaScript. Let's rewrite our sensor logic:

const readings = Array.from({ length: 1000000 }, () => Math.random() * 100);

console.time('Optimized-Loop');
let sum = 0;
let count = 0;

for (let i = 0; i < readings.length; i++) {
  const val = readings[i]; // Cache the value to avoid repeated array access
  if (val > 10) {
    sum += val;
    count++;
  }
}
const average = sum / count;
console.timeEnd('Optimized-Loop');

By doing this, we've moved from O(2n) (two passes over the data) to O(n) (one pass), and we've reduced our memory overhead from O(n) to O(1). We aren't creating a temporary array; we're just updating two numbers in memory.

A Note on Memory Locality

You'll notice I used const val = readings[i]. In a small loop, this doesn't matter. In a million-iteration loop, reducing the number of times the engine has to look up an index in the array can provide a tiny but measurable boost. It's not always necessary, but when you're in "optimization mode," every micro-decision counts.




📋 Practical Task

Optimizing the Transaction Log Processor

You have been given a dataset of 500,000 transaction objects. Each object looks like { id: 123, amount: 50.00, status: 'completed' }. Currently, the code uses a combination of .filter() and .map() to get an array of amounts for only the 'completed' transactions, and then calls another function to sum them up.

Your Task: Rewrite the processing logic to calculate the total sum of 'completed' transactions using a single for loop. Your solution must avoid creating any intermediate arrays to minimize memory pressure and improve execution speed.

const transactions = Array.from({ length: 500000 }, (_, i) => ({
  id: i,
  amount: Math.random() * 100,
  status: Math.random() > 0.2 ? 'completed' : 'pending'
}));

// Rewrite the logic below to optimize for performance
let totalCompletedAmount = 0;

// Your optimized loop goes here...

console.log(`Total: ${totalCompletedAmount}`);
Rating
0 0

There are no comments for now.

to be the first to leave a comment.