skip to content

Why do two sequential loops over n records cost O(n), but one nested inside the other O(n^2)?

level: juniorimportance: must knowfreq 88%

answer

  1. ask what the loops share
  2. does the second loop restart per item?
  3. side by side means totals add
  4. one inside the other multiplies trip counts
  5. constants and lower-order terms drop

basics

~20 s

Loops side by side add their trip counts: n + n = 2n, and constants drop, so it stays O(n). Nesting multiplies instead: the inner loop restarts in full for every outer step, giving n * n = O(n^2).

solid answer

~40 s

The rule is add for sequential, multiply for nested. Two loops one after another each run n times, so the total is `n + n = 2n`; big-O drops the constant factor and reports O(n). When one loop sits inside the other, the inner loop runs to completion **once per outer iteration**, so the body executes `n * n` times and the cost is O(n^2). The thing to count is not how many `for` keywords appear in the code but how many times the innermost body actually executes. That is also why adding a third sequential pass keeps it linear — three passes is still O(n) — while adding a third level of nesting makes it cubic. And when the terms differ, the sum keeps only the dominant one: `n^2 + n` is O(n^2).

go deeper

for a junior

Recall the two rules by name — sequential adds, nested multiplies — and apply them to a short fragment out loud. Be ready to say how many times the innermost line runs.

for a middle

Explain why the inner loop restarts in full on each outer pass, and handle differing bounds: an outer n and an inner m is O(n*m), not O(n^2). Show the sum keeping only its dominant term.

for a senior

Demonstrate that you spot accidental nesting in real code, where the inner loop is hidden behind a helper call or a lookup that scans. Estimate the practical impact at production data sizes, not just the class.

for a principal

Own the guidance your team follows: which growth classes are acceptable for which data volumes, and when a documented linear-pass merge is worth the readability cost versus when only a structural change to the algorithm will do.

## The two composition rules Almost all iterative complexity analysis reduces to two rules about how loop costs compose. **Sequential composition adds.** If one block costs `f(n)` and the block after it costs `g(n)`, running both costs `f(n) + g(n)`. Since big-O keeps only the dominant term and drops constant factors, `n + n = 2n` is O(n), and `n^2 + n` is O(n^2) — the linear pass is invisible next to the quadratic one. **Nested composition multiplies.** If a loop runs `a` times and its body costs `b` each time, the total is `a * b`. An inner loop nested in an outer one restarts from scratch on every outer iteration, so its full cost is paid `a` times over. ## Why the multiplication is the surprising one A loop that runs n times and does constant work per pass costs O(n) — this feels obvious. The trap is that people count *loops in the source text* rather than *executions of the innermost statement*. Consider a nightly job over n settled payments: - Pass 1 normalises every record. Pass 2 sums every record. Pass 3 writes every record. Three loops, three passes over the data, `3n` operations, O(n). Tripling the record count triples the runtime. - Now instead put the summing loop *inside* the normalising loop. The source has the same two loops, but the sum is recomputed for every single record: `n * n` operations. Tripling the record count makes it **nine** times slower. That difference — a factor of 3 versus a factor of 9 for the same input growth — is what the notation is trying to communicate, and it is entirely determined by whether one loop is beside the other or inside it. ## Counting the innermost body The reliable method is mechanical: walk from the outside in, and for each loop write down how many times it runs *given* the loops enclosing it. Multiply down a nesting chain, add across siblings. For a loop over n regions, each containing a loop over the m stores in that region: ``` total = sum over regions of (stores in that region) ``` If every region has m stores, that is `n * m` — and note this is O(n*m), **not** O(n^2), because n and m are independent inputs. Collapsing two different sizes into one symbol is a common early mistake: if there are 10,000 regions and 3 stores each, calling it "quadratic" wildly misdescribes the work. State the bound in terms of every input that actually varies. ## Constants, and why they are dropped but not irrelevant Big-O deliberately discards the constant: O(2n), O(3n) and O(n/2) are all written O(n), because the notation describes how cost *scales* with input size, not how many nanoseconds it takes. Three sequential passes really is three times more work than one — but both triple when the input triples, so they belong to the same growth class. Say "three linear passes, O(n)" rather than "O(3n)"; the first is precise about both facts. The reverse error matters more: a candidate who has learned "drop constants" sometimes concludes that constants never matter. They do — they just do not change the class. Merging three passes into one is a real, worthwhile optimisation when the data is large and each pass touches memory; it is simply not a complexity improvement, and it will never rescue an accidentally quadratic loop. ## What an interviewer is checking They want to hear the two rules named, applied to the fragment on the whiteboard, and stated in terms of the innermost body's execution count. The strong answer also flags when the two loop bounds are different inputs, and does not inflate a sequence of passes into a higher power. Being able to say "this is O(n^2) because the inner loop restarts for each of the n outer steps, so the comparison runs n times n" is the whole of what is being asked.

  • What if the outer loop runs over n regions and the inner over m stores — is that O(n^2)?
    No, it is O(n*m). Nesting multiplies the two trip counts, but n and m are independent inputs and collapsing them into one symbol misdescribes the work. With 10,000 regions and 3 stores each, the cost is essentially linear in regions. Always state the bound in terms of every input that actually varies.
  • A loop over n contains a nested loop over n, and after it a separate loop over n runs. Total cost?
    O(n^2). The nested part costs n*n and the sequential pass costs n, so the total is n^2 + n. Sequential composition adds, and big-O keeps only the dominant term, so the linear pass disappears next to the quadratic one. It is still real work, just not what determines scaling.
  • Does merging three sequential passes into one improve the complexity?
    No — 3n and n are both O(n), so the class is unchanged. It can still be a genuine speedup, roughly threefold, and it can matter a lot when each pass re-reads a large data set. Constants are dropped from the notation, not from reality; they just never rescue an accidentally quadratic loop.

Two sequential loops are two errands run one after the other: total time is the sum. A nested loop is repeating the second errand in full at every stop on the first — the trip count multiplies.

saying these in an interview costs you the question

  • Counts loops in the source instead of executions of the innermost body
  • Says three sequential loops are O(n^3)
  • Writes O(3n) and insists the constant belongs in the class
  • Calls a loop over n regions times m stores O(n^2)
  • Concludes that dropped constants mean constants never matter

context