🧩 Coding Interviews · ch.07 · prefix sums & intervals
🧩 Part II · Linear Structures · chapter 7 / 24

Precompute once,
answer forever

A running total turns any range-sum question into one subtraction — pay O(n) up front, then answer every query in O(1). The same energy then hits the calendar: sort the intervals, merge the overlaps, and sweep the starts and ends to a peak.

Two patterns share this chapter because they share a soul: do the organizing work once, up front, so every question afterwards is nearly free. Prefix sums pre-add an array so any range sum becomes a single subtraction. Interval problems pre-sort a calendar so overlap becomes a purely local question — you only ever compare neighbors.

Both are also famous team players. Prefix sums plus Chapter 4's hash map produce Subarray Sum Equals K — the book's first genuine combo pattern, the kind of seam Chapter 23 teaches you to hunt. And the interval sweep is a warm-up for Chapter 19's greedy proofs and Chapter 14's heaps, which both love a sorted timeline.

1Pay once, answer forever — the running total

Start with the honest baby version: Range Sum Query – Immutable Easy. You get a fixed array and then a stream of queries, each asking "what's the sum from index i to index j?" The naive move loops over the range every time — O(n) per query. With 10⁵ queries on a 10⁵-element array that's 10¹⁰ operations, and Chapter 3's budget meter buries you.

The fix is a bank statement. Keep a ledger P where P[t] is the running balance after the first t elements — with P[0] = 0 for the empty start. Then any range collapses to one subtraction:

sum(i..j) = P[j+1] − P[i]

Read it like money: the balance after day j, minus the balance before day i, is exactly what happened in between. Build the ledger in one O(n) pass, and every query after that costs two lookups and a minus sign.

💡
A prefix sum is just a fold whose intermediate values you refused to throw away. scanLeft(0)(_ + _) in Scala, itertools.accumulate in Python — the language already knows this trick; the pattern is remembering to reach for it.
⚠️
The off-by-one vaccine: give P exactly n + 1 entries with that leading zero. Skip the sentinel and every range starting at index 0 becomes a special case — the classic way to burn three whiteboard minutes on an Easy.
🎯
The tell: "sum of a range" plus "many queries" — or any repeated aggregate over slices of an array that doesn't change — → prefix sums. The word "immutable" in a problem title is the interviewer handing you the pattern in gift wrap.

2Two lookups, any range — the ledger in action

Watch the ledger get built (one pass, each cell = previous cell + one element), then interrogate it: click any two array cells and the machine answers with exactly two shelf lookups. The stat cards keep score of what the naive re-scanner would have paid for the same questions — the gap is the whole pattern.

Interactive · the prefix ledger Click two array cells to pick a range — the subtraction lights up
building the ledger…
range sum
ledger cost (all queries)
0 ops
naive cost (all queries)
0 adds
queries answered
0

Note that the values include negatives and the ledger doesn't care — subtraction works on any running total. Hold that thought; it's about to matter a lot.

3Have I seen this prefix? — the hash-map handshake

Now the interview classic: Subarray Sum Equals K Medium. Count how many contiguous subarrays sum to exactly k — negatives allowed. Brute force tries every (i, j) pair: O(n²) — every start times every end. The pattern does it in one pass, and the derivation is three lines of algebra:

  • A subarray ending here sums to k ⇔ prefix_here − prefix_earlier = k.
  • Rearranged: prefix_earlier = prefix_here − k.
  • So while scanning, ask: "how many earlier prefixes equal prefix − k?" — and that "have I seen…" question is Chapter 4's hash map, answering in O(1).

Keep a map from prefix value → how many times it has occurred. At each element: update the running prefix, add seen[prefix − k] to the count, then record the current prefix. One pass, O(n) time and space. It's the Two Sum move played on the ledger instead of the array.

⚠️
The forgotten seed: initialize the map as {0: 1} — the empty prefix has been "seen" once. Skip it and every subarray that starts at index 0 silently vanishes from your count. This is the single most common bug in this problem, and interviewers know it.
🎯
The tell: "count / how many subarrays with sum exactly k", negatives allowed → prefix + hash map. Chapter 6's sliding window will whisper to you — ignore it: with negatives, growing the window can shrink the sum, so "too big → shrink" stops being true and the window's whole logic collapses. All-positive input with "at most / at least"? Then the window bids again.

4Same trick, other coats — products, XOR, minima

"Running total" is not really about addition — it's about any operation you can later undo. A few coats the same trick wears:

  • Prefix products. Product of Array Except Self Medium, Chapter 4's finale: each answer is (product of everything left of me) × (product of everything right of me) — a prefix pass and a suffix pass, no division needed.
  • Prefix minimum. Chapter 6's Best Time to Buy & Sell Stock Easy kept a "cheapest price so far" — that's a prefix min in a trench coat.
  • Prefix XOR. XOR is its own inverse, so xor(i..j) = PX[j+1] ^ PX[i] — the same subtraction trick with ^ instead of . Chapter 22 makes a whole party out of this.
  • 2-D prefixes. An "integral image" pre-adds a matrix so any rectangle's sum is four lookups instead of two. Same idea, one dimension fancier.

One honest boundary: the subtraction trick needs an inverse. Sums, products, XOR — fine. Range min or max queries have no undo (you can't "subtract" a minimum), so prefixes only give you "so far" scans there. Arbitrary range-min needs a segment tree or sparse table — genuinely rare in interviews, and saying that sentence out loud is itself worth signal points.

5Calendars, not arrays — sort by start, then merge

Second family, same soul. An interval is a [start, end] pair — a meeting, a booking, a range of taken seats — and interval problems arrive as an unsorted pile of them. Unsorted, "who overlaps whom?" is an all-pairs question: O(n²) — everyone against everyone. The organizing payment here isn't a ledger, it's a sort by startO(n log n), sort once, then walk. After it, overlap becomes local: as you walk left to right, the only thing a new interval can possibly overlap is the block you're currently building. Everything earlier ended too soon; everything later starts too late.

That makes Merge Intervals Medium a five-line walk. Keep a current block [s, e]. For each next interval, one comparison: next.start ≤ e means overlap — fuse by extending e = max(e, next.end). Otherwise there's a gap: commit the block, start a new one. Its sibling Insert Interval Medium hands you the list already sorted and one newcomer: emit everything that ends before the newcomer starts, fuse everything that overlaps it into one block, emit the rest. Three phases, one pass, no sort needed — the input paid the organizing cost for you.

Interactive · the interval merger Drag any bar along the timeline, then sort & merge
drag bars, then sort
state
unsorted
merged blocks
overlaps fused
0
comparisons
0
⚠️
The nesting trap: when fusing, extend with e = max(e, next.end) — never plain e = next.end. Feed [1,10], [2,3] to the plain version and your merged block shrinks to end at 3. Nested intervals are the test case interviewers keep in their back pocket.
🎯
The tell: anything with start/end times — meetings, jobs, reservations, ranges — → sort by start and walk. If the statement says the input is already sorted, the sort is pre-paid: your answer should be O(n), and saying so is free signal.

6How many at once — the event sweep

Meeting Rooms II Medium asks a different question: not "what does the merged calendar look like" but "how many rooms do I need?" — which is really "what's the maximum number of meetings alive at one instant?" Merging can't answer that; merging deliberately forgets how thick the pile was.

So change representation. Explode every meeting into two events: (start, +1) and (end, −1). Sort all events by time and sweep left to right with a running counter — +1 when a meeting begins, −1 when one ends. The counter traces occupancy over time, and its peak is the answer. O(n log n) — sort once, then walk; the same bill as merging, buying a different fact.

One subtlety earns real points: ties. If a meeting ends at 10:00 and another starts at 10:00, process the end first — the room frees up in time. Sort ends before starts at equal times, or you'll book a phantom room.

Interactive · the room sweeper Step through the +1/−1 events; the counter's peak = rooms needed
▲ = start (+1) · ▼ = end (−1)
rooms in use now
0
peak = rooms needed
0
events processed
0 / 12

There's a popular twin solution: sort meetings by start and push end-times into a min-heap, reusing a room whenever the earliest end precedes the next start. Same answer, same complexity — and when the heap chapter (Chapter 14) hands you that tool, you'll recognize this problem waiting for it. For the interview, the sweep is easier to prove and harder to fumble.

7Keep the most, cancel the least — scheduling and the traps

One more family member, because it looks identical and sorts differently. Non-overlapping Intervals Medium: remove the fewest intervals so none overlap. Flip it — keep the most compatible intervals — and the winning move is to sort by end and greedily keep every interval that starts after the last kept one ended. Why end, not start? The earliest-ending interval leaves the most room for everyone after it; swapping it for any other kept choice never helps. That one-sentence swap argument is Chapter 19's exchange proof making an early cameo — greedy is legal here, and you just proved it.

And the endpoint fine print, which decides real test cases:

  • Touching intervals. Do [1,4] and [4,6] overlap? Merge Intervals says yes (they fuse to [1,6]: the test is start ≤ end); scheduling problems usually say no (back-to-back meetings are fine: the test is start < end). Same picture, opposite verdicts — this is a Chapter 2 clarifying question, not a guess.
  • Which sort? Merging and sweeping sort by start; keep-the-most scheduling sorts by end. Say which one you're using and why — it's a one-line proof of understanding.
🎯
The tell: "maximum number of non-overlapping…" or "fewest removals / cancellations" → sort by end + greedy keep. "Merge / how many at once" → sort by start. The verb in the problem statement picks your sort key.

8One chapter, two moves — the whiteboard skeleton

Zoom out and this chapter is one idea wearing two costumes. The naive versions of every problem here re-scan: re-add the range, re-compare all interval pairs — quadratic work that repeats itself. The pattern pays a one-time organizing cost — an O(n) ledger or an O(n log n) sort — after which a single linear walk (or an O(1) lookup) finishes the job. When you name the pattern in the room, name the payment too: "I'll build prefix sums in O(n), then each query is O(1)" is a complete, scored sentence.

The pattern, as a whiteboard skeleton:

  1. 1Range sums over a fixed array? Build the ledger: P[0] = 0, P[t+1] = P[t] + a[t] — one pass, n + 1 entries.
  2. 2Answer any range as sum(i..j) = P[j+1] − P[i] — two lookups, O(1) per query.
  3. 3"Exactly k" counting: one pass — count += seen[prefix − k], then seen[prefix] += 1. Seed seen = {0: 1}.
  4. 4Start/end times? First clarify the endpoints: does touching ([1,4], [4,6]) count as overlap?
  5. 5Merge: sort by start; keep [s, e]; while next.start ≤ e: e = max(e, next.end); else emit and restart.
  6. 6"How many at once": explode to (start, +1) / (end, −1) events; sort with ends before starts; running counter; report the peak.
  7. 7"Keep the most / remove the fewest": sort by end, greedily keep what starts after the last kept end (ch. 19's exchange argument).
  8. 8Say the costs: ledger O(n) build then O(1) per query; every interval move is O(n log n) — sort once, then walk.

9Two patterns shake hands — Subarray Sum Equals K

The flagship is the combo: Subarray Sum Equals K Medium, prefix sums carrying the ledger and Chapter 4's hash map remembering it. The Python version is the imperative skeleton you'd write on a whiteboard; the Scala version makes the structure explicit — the running prefix is a fold, the seen-map is the fold's memory, and the whole solution is one foldLeft carrying a triple. Flip between them and notice it's the same five lines wearing two syntaxes.

💡
Interview narration for free: "a subarray sum is a difference of two prefixes, so I'm counting pairs of prefixes that differ by k — and counting pairs with a target difference is a hash-map job." Two sentences, two patterns named, derivation complete. That's the whole solve, out loud.