JavaScript
algorithm-optimization
time-complexity
performance-improvement
coding-efficiency

Changing On3 to On2 in JavaScript

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Moving from O(n^3) to O(n^2) is usually not about writing "faster JavaScript." It is about removing one entire dimension of repeated work. In practice that means recognizing which inner-loop computation can be replaced with precomputed data, a hash lookup, or a sorted traversal.

Where O(n^3) Usually Comes From

A cubic algorithm often appears as three nested loops over the same dataset. For example, suppose you want to know whether any three numbers sum to a target:

javascript
1function hasTripleSumSlow(values, target) {
2  for (let i = 0; i < values.length; i += 1) {
3    for (let j = i + 1; j < values.length; j += 1) {
4      for (let k = j + 1; k < values.length; k += 1) {
5        if (values[i] + values[j] + values[k] === target) {
6          return true;
7        }
8      }
9    }
10  }
11
12  return false;
13}
14
15console.log(hasTripleSumSlow([1, 4, 7, 10, 12], 21));

This code is correct, but it checks every triple explicitly. For large inputs, that becomes expensive quickly.

The important observation is that the third loop is only answering one question: "Given values[i] and values[j], does there exist a matching third value?" Once you see the question clearly, you can often replace the loop with a faster lookup.

Reducing One Loop with a Set

One common technique is to keep two loops and replace the innermost scan with a Set membership check.

javascript
1function hasTripleSumBetter(values, target) {
2  for (let i = 0; i < values.length; i += 1) {
3    const seen = new Set();
4
5    for (let j = i + 1; j < values.length; j += 1) {
6      const needed = target - values[i] - values[j];
7
8      if (seen.has(needed)) {
9        return true;
10      }
11
12      seen.add(values[j]);
13    }
14  }
15
16  return false;
17}
18
19console.log(hasTripleSumBetter([1, 4, 7, 10, 12], 21));

This is O(n^2) on average because:

  • the outer loop runs n times
  • the inner loop runs about n times
  • each Set lookup is constant time on average

The exact problem does not matter much. The pattern is what matters: if the third loop is just checking membership, counting occurrences, or finding a complement, a hash-based structure usually removes it.

Another Strategy: Sort and Use Two Pointers

Sometimes the cubic code is comparing ordered values, ranges, or sums. In those cases, sorting once and then scanning with two pointers can reduce complexity.

javascript
1function hasTripleSumSorted(values, target) {
2  const sorted = [...values].sort((a, b) => a - b);
3
4  for (let i = 0; i < sorted.length - 2; i += 1) {
5    let left = i + 1;
6    let right = sorted.length - 1;
7
8    while (left < right) {
9      const sum = sorted[i] + sorted[left] + sorted[right];
10
11      if (sum === target) {
12        return true;
13      }
14
15      if (sum < target) {
16        left += 1;
17      } else {
18        right -= 1;
19      }
20    }
21  }
22
23  return false;
24}
25
26console.log(hasTripleSumSorted([1, 4, 7, 10, 12], 21));

Sorting costs O(n log n), but the main scan is O(n^2), so the total remains quadratic.

This approach is useful when:

  • comparisons depend on numeric ordering
  • you need deterministic traversal
  • duplicate handling is easier after sorting

A More General Optimization Mindset

When you see O(n^3), do not immediately reach for syntax tricks. Ask what each loop is contributing.

Typical outcomes:

  • One loop is searching for a value that could be found with a Map or Set.
  • One loop recomputes the same partial result many times and should be cached.
  • One loop exists only because the data is unsorted.
  • The problem can be reformulated into pair processing instead of triple processing.

For example, if an algorithm repeatedly counts frequencies inside an inner loop, precompute the counts once:

javascript
1function countPairsWithDifference(values, diff) {
2  const counts = new Map();
3
4  for (const value of values) {
5    counts.set(value, (counts.get(value) || 0) + 1);
6  }
7
8  let total = 0;
9  for (const value of values) {
10    total += counts.get(value + diff) || 0;
11  }
12
13  return total;
14}
15
16console.log(countPairsWithDifference([1, 2, 3, 4, 5], 2));

This example is not a triple-loop rewrite, but it shows the same principle: do shared work once, then reuse it.

When O(n^2) Is Still the Best You Can Do

Not every cubic algorithm can be reduced further without changing the problem. Some tasks inherently depend on examining all pairs, and O(n^2) is already the right target. That is still a major improvement. If n is 10,000, reducing from cubic to quadratic changes the problem from effectively unusable to potentially manageable.

The real goal is not to chase a specific Big O label. The goal is to match the algorithm to the information you actually need at each step.

Common Pitfalls

  • Replacing loops without understanding the repeated work. If you do not know what the inner loop is doing, the rewrite will be accidental rather than correct.
  • Claiming Set or Map makes everything constant time in all cases. Hash lookups are average-case constant time, not a guarantee of zero cost.
  • Forgetting duplicate rules when switching to sorting or hash-based logic. Test cases with repeated values often expose bugs.
  • Mutating the original input by sorting it in place. Use a copy such as [...values] if callers expect the original order.
  • Optimizing asymptotic complexity while ignoring correctness. Verify the new algorithm against small examples before benchmarking.

Summary

  • Cutting O(n^3) to O(n^2) usually means removing one repeated search.
  • 'Set and Map help when the innermost loop is doing membership or frequency work.'
  • Sorting plus two pointers is a strong option for ordered numeric problems.
  • Focus on what each loop contributes, not on syntax alone.
  • Preserve correctness first, then measure the performance improvement.

Course illustration
Course illustration

All Rights Reserved.