skip to content

questions

13

Why do two O(1) writes to a difference array record an increment across a whole index range?

level: juniorimportance: must knowfreq 70%

answer

  1. think about storing gaps, not values
  2. what does a running total do after a bump
  3. one mark opens the range, one closes it
  4. the closing mark sits one past the end
  5. range increment = two suffix increments

basics

~20 s

A difference array stores the gap between neighbouring entries. Adding v at index l and subtracting v at index r+1 raises the running total by v for exactly positions l through r, and one prefix pass turns those two marks back into real values.

solid answer

~40 s

A difference array `diff` holds the gap between consecutive entries of the target array, so the real value at index `i` is the running total `diff[0] + ... + diff[i]`. Adding `v` at `l` lifts every running total from `l` onward by `v`; subtracting `v` at `r+1` cancels that lift from `r+1` onward. The net effect is exactly `+v` on `l..r`, paid for with two constant-time writes. Nothing in the real array has changed yet — you pay one O(n) prefix pass at the end that materializes every recorded update at once. For `m` batched adjustments over `n` shelf positions in a warehouse count array that is O(m + n) work, instead of the O(m·n) you get from looping over each range and touching every position it covers.

go deeper

for a junior

Be ready to state the two writes and the final prefix pass from memory, and to say why the recorded values are not real until that pass runs.

for a middle

Explain the telescoping sum that makes the encoding reversible, and derive the O(m + n) total against the O(m·n) of applying each range directly.

for a senior

Show the judgment that the pattern needs a write phase that closes before the read phase, and name what kind of workload violates that precondition.

for a principal

Own the call on whether the cleverness is worth it: a two-line loop is obvious to every maintainer, while a deferred-materialize array invites a colleague to read it one refactor too early.

## What a difference array actually stores Given a target array `a` of length `n`, its difference array `diff` stores gaps rather than values: ``` diff[0] = a[0] diff[i] = a[i] - a[i-1] for i >= 1 ``` That encoding is losslessly reversible: the running total of `diff` up to index `i` telescopes back to `a[i]`, because `(a[0]) + (a[1]-a[0]) + (a[2]-a[1]) + ... + (a[i]-a[i-1])` cancels everything but `a[i]`. So a single left-to-right pass carrying one accumulator reconstructs the whole array in O(n) time and O(1) extra space beyond the output. In interview problems you rarely build `diff` from an existing array. You start with an all-zero `diff` representing an all-zero target, record a pile of range increments into it, and materialize once. ## Why two writes are enough The accumulator is a running sum, so anything you add at position `l` is carried forward through every later index. That one write already performs "add `v` to every index from `l` to the end" — a suffix increment. The second write, `diff[r+1] -= v`, injects the opposite suffix increment starting one position later. The two suffixes overlap everywhere except `l..r`, and where they overlap they cancel exactly: | index range | contribution of `+v` at `l` | contribution of `-v` at `r+1` | net | |---|---|---|---| | `0 .. l-1` | 0 | 0 | 0 | | `l .. r` | `+v` | 0 | `+v` | | `r+1 .. n-1` | `+v` | `-v` | 0 | That is the whole trick: a range increment is the difference of two suffix increments, and a suffix increment is one write into a running-sum encoding. ## The cost argument Suppose a warehouse system applies `m` batched stock adjustments, each of the form "add `v` units to every shelf position from `l` to `r`", over an array of `n` shelf positions. - **Direct application.** Each adjustment writes to every position it covers. A range can cover the whole aisle, so the worst case is O(m·n). With 200,000 adjustments over 100,000 positions that is up to 2 × 10^10 writes — hopeless. - **Difference array.** Each adjustment is two writes: O(m) total. One materialize pass is O(n). Total O(m + n) — about 500,000 operations for the same input. Space is O(n) for the difference array (plus one slot; see the boundary discussion below), which is the same order as the array you are already holding. Note what the O(1) claim does and does not say. Each *update* is O(1), genuinely and in the worst case — not amortized, not expected. But the *array* is not correct after those updates; correctness is deferred to the prefix pass. The pattern trades read-time freshness for write-time speed. ## When the trick applies Three conditions have to hold: 1. **The updates are range increments** — add the same delta to a contiguous stretch. Range *assignment* ("set every position in `l..r` to `v`") does not decompose into two additive marks, because a later assignment must erase earlier ones rather than accumulate with them. 2. **The deltas compose additively.** Increments commute, so the order in which you record them does not matter and overlapping ranges simply sum. This is why you can record all `m` updates before doing any work. 3. **The reads come after the writes.** The array is only trustworthy once you have run the prefix pass. ## The two classic mistakes The first is closing the range at `r` instead of `r+1`. Writing `diff[r] -= v` cancels the increment one position early, so the last element of every range silently misses its update — a bug that passes small hand-traced examples where `l == r` is never exercised, then fails on real data. The second is forgetting that the closing write can land at index `n`, one past the end, when a range runs to the last position. Either size the difference array `n+1` and ignore the final slot, or guard the write; both are correct, and the extra slot is never read by a materialize pass that stops at `n-1`. ## Rebuilding intuition If you ever blank on the signs mid-interview, re-derive rather than recall: write down a five-element zero array, apply "+3 on indices 1..3" by hand, and take successive differences of the result. You get `0, +3, 0, 0, -3` — the `+3` sits at the left endpoint and the `-3` sits at one past the right endpoint, which is exactly the two writes. That thirty-second derivation is more reliable under pressure than a memorized formula, and interviewers notice when a candidate can rebuild a rule instead of reciting it.

  • What does the array hold immediately after those two writes, before any prefix pass?
    Nothing usable as a value array — it holds gaps, not counts. Reading `diff[i]` gives the delta between neighbours, so a caller that reads it directly sees garbage. The recorded updates only become real values after the running-sum pass. That deferral is the price of O(1) updates, and it is why the pattern only fits workloads whose reads come after the write batch closes.
  • If two adjustments cover overlapping shelf ranges, do you need to do anything special?
    No. Increments compose additively, so overlapping ranges just accumulate: each contributes its own `+v` and `-v` marks, and the running sum adds them up wherever they overlap. Order does not matter either, since addition commutes. This is exactly why you can record all `m` updates blindly and materialize once — a property that range *assignment* does not have.
  • How would you undo one recorded increment before materializing?
    Record its negation: `diff[l] -= v` and `diff[r+1] += v`. Because the encoding is additive, an increment and its negation cancel to zero at every index, and the retraction is still two constant-time writes. After materializing, though, undoing is no longer cheap — you would have to re-difference the array first or replay the batch.

It is like flagging a stretch of shelving with a start flag and a stop flag instead of relabelling every shelf: one walk down the aisle carrying a running count applies every flag pair at once.

saying these in an interview costs you the question

  • Says the array is correct right after the two writes
  • Closes the range at r instead of r+1
  • Claims difference arrays speed up reads as well as writes
  • Thinks overlapping ranges must be merged first
  • Applies the same trick to range assignment, not increment

context

open as a page

How does a running total plus a map of seen totals count zero-net stretches in a signed ledger?

level: juniorimportance: must knowfreq 82%

basics

~20 s

Keep a running total of the signed deltas and a map counting how often each running total has already appeared. Two positions with the same running total bracket a stretch that nets to zero, so each earlier match adds one more qualifying stretch.

open as a page

Why does a prefix-sum array answer range sums in O(1), and why is it sized n+1?

level: juniorimportance: must knowfreq 72%

basics

~20 s

A prefix array stores running cumulative totals, so any range sum becomes one subtraction: the total over positions l..r equals P[r+1] - P[l]. The extra leading slot holds 0, so ranges that begin at index 0 need no special case.

open as a page

In prefix-sum counting with a hash map, why must the empty prefix (total 0) be seeded first?

level: middleimportance: must knowfreq 68%

basics

~20 s

Without a seeded entry for total 0, every qualifying stretch that begins at the first element is lost. Those stretches need the running total from before any element was read, and the only way the map can offer it is to record total 0 once up front.

open as a page

Why is a difference array called the inverse of a prefix sum, and when does that duality pay off?

level: middleimportance: should knowfreq 50%

basics

~20 s

Taking running sums and taking successive differences undo each other, so either transform recovers the original array. That duality splits the work: prefix arrays make reads cheap and writes useless, difference arrays make range writes cheap and defer all reading to one pass.

open as a page

In a difference array of length n, what breaks when a range update ends at the last index?

level: middleimportance: should knowfreq 55%

basics

~20 s

The closing write lands at index r+1, which equals n when the range ends at the last position — one slot past the array. Fix it by sizing the difference array n+1 and ignoring the extra slot, or by skipping that write when r+1 equals n.

open as a page

In the +1/-1 prefix-sum trick for equal-counts runs, why store each total's first index?

level: middleimportance: should knowfreq 58%

basics

~20 s

Because length is measured from the earliest position holding that running total. Recode the two outcomes as +1 and -1, and a run is balanced exactly when its two end totals match; keeping the first index and never overwriting it makes every later match yield the longest possible run.

open as a page

In a 2D prefix-sum table, why is one corner term added back in the submatrix formula?

level: middleimportance: should knowfreq 45%

basics

~20 s

Because the two subtracted strips overlap. Removing everything above the submatrix and everything to its left takes the top-left corner rectangle away twice, so it must be added back once to leave each cell counted exactly once.

open as a page

Why does a difference array stop paying off when reads interleave with the range updates?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Every read forces a materialize pass, so with reads and updates alternating you pay O(n) per read and the total collapses back to O(m·n) — the same cost as applying each range directly, plus the overhead of the encoding.

open as a page

How do you count contiguous runs whose total is divisible by k using prefix totals?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Bucket running totals by their remainder modulo k instead of by their exact value. Two positions in the same remainder bucket bracket a run whose total divides evenly by k, so count pairs per bucket, seeding remainder 0 with one for the empty prefix.

open as a page

Why does a sliding window fail to find a stretch summing to a target when values can be negative?

level: seniorimportance: should knowfreq 52%

basics

~20 s

A two-pointer window relies on the sum being monotone in the window's extent: growing never lowers it, shrinking never raises it. Negative values break that, so a window discarded as too large may be exactly the one you needed. Prefix totals plus a map of seen totals assume no monotonicity at all.

open as a page

When does maintaining a prefix-sum array cost more than scanning the range each time?

level: seniorimportance: should knowfreq 42%

basics

~20 s

When the underlying values change. A point edit invalidates every cumulative total after it, forcing an O(n) rebuild, so an edit-heavy workload pays O(n) per write to save O(n) per read — and with few queries, the O(n) build never amortizes at all.

open as a page

Prefix products and prefix XOR: which part of the two-prefix subtraction trick still works?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

XOR transfers cleanly: it is its own inverse, so the range XOR is X[r+1] XOR X[l]. Products do not, because division fails — one zero anywhere in the prefix destroys every later quotient, and exact division is not always available.

open as a page