Reading

Big-O and JavaScript's Hidden Costs

Learn to describe growth, price every operation inside a loop, and defend a complexity analysis.

55 min 4 objectives
Learning objectives
  • Explain what Big-O does and does not measure
  • Combine sequential and nested work correctly
  • Detect hidden linear operations inside loops
  • Analyze time and auxiliary space independently

My notes & review

Why interviewers ask about complexity

Two programs can return the same answer and behave completely differently as the input grows. Big-O describes the shape of that growth. It does not predict an exact runtime in milliseconds. It answers a more durable question: if the input becomes ten or one thousand times larger, how does the amount of work change?

Let n represent the relevant input size. Count how often the important work can run, then keep the fastest-growing term and ignore constant multipliers:

Code
3n + 20       → O(n)
n² + 4n + 8   → O(n²)
half of n     → O(n)

Dropping constants does not mean constants never matter in real engineering. It means they do not change the long-run growth class. An O(n) algorithm can be slower for tiny inputs and still scale better than O(n²).

The growth classes you need first

ComplexityMental modelCommon source
O(1)the same bounded workindexing an array, expected Map lookup
O(log n)repeatedly discard a fixed fractionbinary search
O(n)visit the input oncea full scan
O(n log n)divide into levels, process each levelefficient comparison sorting
O(n²)compare many pairstwo full nested loops
O(2ⁿ)make two choices per itemenumerating all subsets
How common growth functions compare Linear axes: input size 1 through 10, work 0 through 100. The exponential curve exceeds 100 near input size 7 and is clipped. Exact values at input size 10 appear below. Illustrative work (linear scale) 0 25 50 75 100 1246810 Input size (n)
  • 1: 1 at n = 10
  • log₂ n: 3.3 at n = 10
  • n: 10 at n = 10
  • n log₂ n: 33.2 at n = 10
  • n²: 100 at n = 10
  • 2ⁿ: 1024 at n = 10
The graph plots representative functions, not measured runtimes. The vertical axis stops at 100: 2ⁿ quickly leaves the chart, reaching 1,024 at n = 10. Logarithmic and constant growth stay near the bottom. Constants and implementation details can change actual performance.

Add sequential work; multiply nested work

Two separate full scans take n + n = 2n visits, so they are O(n):

Code
for (const value of values) inspect(value)
for (const value of values) record(value)

If the inner loop runs n times for every one of n outer iterations, the work is n × n, or O(n²):

Code
for (const left of values) {
  for (const right of values) compare(left, right)
}

Different input sizes deserve different names. Comparing every item in a with every item in b is O(a · b), not automatically O(n²). Precision makes your explanation stronger and prevents you from hiding an expensive input behind the wrong variable.

Loop Complexity: Include Each Operation’s Cost

A loop is not automatically O(n). Multiply the number of iterations by the cost of its body. Array.prototype.includes may scan the entire array, so this apparently simple code is O(n²):

Code
for (const value of values) {
  if (otherValues.includes(value)) return true
}

The same warning applies to indexOf, copying with spread, slicing a growing range, front insertion/removal, and many string-building strategies. Method names can hide loops; complexity analysis must look through the method name.

Time and space answer different questions

Time complexity measures work. Auxiliary space measures memory created by the algorithm beyond the input and required output. A Set holding every input value costs O(n) extra space. Five counters cost O(1). A recursive call chain can consume O(n) stack space even when no array or Map is allocated explicitly.

Do not count the input as extra space, and clarify whether an output array is required by the problem. Interviewers often exclude required output from auxiliary-space analysis, but saying your convention removes ambiguity.

Amortized vs. Expected Time Complexity

Appending to a dynamic array is amortized O(1). Most appends are constant work; occasionally the engine allocates larger storage and copies existing elements. Spread that occasional cost across many appends and the average per append is constant.

For a concrete model, imagine an array with room for four values. The first four appends write directly into empty slots. The fifth append cannot fit, so the runtime allocates a larger area, copies the four existing values, and then writes the fifth. That single append costs O(n), but it buys empty slots for several cheap future appends. Amortized analysis charges the rare copy across the many operations that benefit from it.

Map and Set operations are usually described as expected O(1). Their performance depends on good hash distribution and controlled load. Unlike amortized analysis, “expected” is about typical distribution rather than spreading a guaranteed occasional cost across a sequence. Week 2 explains that machinery.

Common mistakes

  • Reporting only time and forgetting space.
  • Calling all nested loops O(n²), even when an inner pointer advances only n times total.
  • Treating sort() as free; comparison sorting is generally O(n log n).
  • Ignoring a linear helper called inside a linear loop.
  • Optimizing constants while leaving a worse growth class untouched.
Checkpoint

Audit a loop

Choose a recent solution. Name its input-size variables, price every operation inside its dominant loop, and state both time and auxiliary space in one complete sentence.