Prefix Sums and Difference Arrays
Precompute running totals once, then answer any range-sum question by subtracting two numbers.
- O(1) per queryAfter an O(n) build
- O(n)Space: prefix array or distinct prefix sums
- n + 1 entriesPrefix array length, with prefix[0] = 0
Show what to say
“When I need many range sums, or a subarray that sums to k even with negatives, I store running totals: a range is prefix[right + 1] minus prefix[left]. With a hash map of earlier prefixes it is one O(n) pass, paid for with O(n) memory.”
Trade-offs
- Prefix sums vs sliding window: a window is O(1) space but needs non-negative values; prefix sums plus a map handle negatives and exact sums.
- Prefix array vs Fenwick tree: the array is O(1) per query but an update costs O(n); a Fenwick tree makes both O(log n).
- Difference array vs looping over each range: O(1) per update plus one O(n) final pass, against touching every cell of every range.
Bring it up before they ask
Keep one convention (with n + 1 entries it is prefix[r + 1] - prefix[l]), seed the map with 0, and look up sum - k before you insert sum.
Keep running totals as you walk a row, and any range sum is one subtraction. A hash map of earlier totals finds subarrays with a given sum in one pass.
When to reach for it
Tap the signals you can see in the problem.
Remembersum of a rangeSubarray sums to krange updates
- 1D prefix sums
What you store
range sum queryprefix[i]= sum of the first i values - Prefix sum + hash map
What you storerunning sum → count or index
subarray sum equals k - 2D prefix sums
What you storesum of the top-left rectangle
range sum query 2D - Difference array
What you storewhere values change
car pooling
The core idea: a range is the difference of two prefixes
Picture a staircase where each step is the total so far.
Read more · 75 words
The sum of any stretch is the height between two steps: top minus bottom.
Let prefix[i] be the sum of the first i values, with prefix[0] = 0. Then:
sum(nums[left..right]) = prefix[right + 1] - prefix[left]
One pass builds prefix. After that every range costs one subtraction, whatever its length.
It works for any values, negatives included, because subtraction undoes addition. It fails for max or min: you can't subtract a max.
Build running sums once, answer any range in O(1)
- i
- null
prefix has 7 slots: prefix[i] will be the sum of the first i values. prefix[0] = 0 is the empty sum, which saves a special case for ranges that start at 0.
function buildPrefix(nums) {
const prefix = new Array(nums.length + 1).fill(0);
for (let i = 0; i < nums.length; i++) {
prefix[i + 1] = prefix[i] + nums[i];
}
return prefix;
}
function rangeSum(prefix, left, right) {
return prefix[right + 1] - prefix[left];
}All steps as text
- prefix has 7 slots: prefix[i] will be the sum of the first i values. prefix[0] = 0 is the empty sum, which saves a special case for ranges that start at 0.
- prefix[1] = prefix[0] + nums[0] = 0 + 3 = 3: just the first value.
- prefix[2] = prefix[1] + nums[1] = 3 + 1 = 4: the sum of the first 2 values.
- prefix[3] = prefix[2] + nums[2] = 4 + 4 = 8: the sum of the first 3 values.
- prefix[4] = prefix[3] + nums[3] = 8 + 1 = 9: the sum of the first 4 values.
- prefix[5] = prefix[4] + nums[4] = 9 + 5 = 14: the sum of the first 5 values.
- prefix[6] = prefix[5] + nums[5] = 14 + 9 = 23: the sum of the first 6 values.
- sum(nums[2..4]) = prefix[5] - prefix[2] = 14 - 4 = 10. prefix[5] covers indices 0 to 4; subtracting prefix[2] removes indices 0 to 1.
- sum(nums[0..5]) = prefix[6] - prefix[0] = 23 - 0 = 23. prefix[6] covers indices 0 to 5; prefix[0] = 0 removes nothing, because the range starts at the beginning.
Play with it
Drag across the staircase: the range sum is the height between two steps. Then try Subarrays = k.
Read more · 86 words
The recipe:
Write the quantity as a range sum
Equal 0s and 1s: sum 0, with each 0 as -1.
Choose the prefix convention
n + 1 entries with
prefix[0] = 0.Turn the condition into a lookup
P(j) - P(i) = kbecomesP(i) = P(j) - k: did an earlier prefix have this value?Decide what the map stores
Counting: how often each prefix occurred, seeded 0 → 1. Longest: the first index, seeded 0 → -1.
Look up, then record
Query
sum - kbefore insertingsum.
Templates
Template 1: running sums for range queries
Range Sum QueryEasy builds once and subtracts per query. Find Pivot IndexEasy needs one running variable: the right side is total - leftSum - nums[i].
// Template 1: running sums. prefix[i] is the sum of the first i values (prefix[0] = 0), so
// sum(nums[l..r]) = prefix[r + 1] - prefix[l] for any range, in O(1) after an O(n) build.
export function buildPrefix(nums) {
const prefix = new Array(nums.length + 1).fill(0);
for (let i = 0; i < nums.length; i++) prefix[i + 1] = prefix[i] + nums[i];
return prefix;
}
export function rangeSum(prefix, left, right) {
return prefix[right + 1] - prefix[left]; // inclusive on both ends
}Template 2: prefix sum + hash map
A subarray ending at j sums to k exactly when an earlier prefix equals sum - k.
Read more · 48 words
That is the lookup from Hash Maps and Sets, on running totals. Keep each prefix's first index to get the longest match.
To count, store how often each prefix occurred (seeded 0 → 1): Subarray Sum Equals KMedium. Contiguous ArrayMedium is the longest version with k = 0, once each 0 counts as -1.
Prefix sum + hash map: longest subarray with sum k
- k
- 3
- sum
- 0
- best
- 0
The map starts with 0 → -1: the empty prefix, "just before index 0", sums to 0. A subarray ending at j sums to 3 when an earlier prefix sum equals sum - 3.
function longestSubarrayWithSum(nums, k) {
const firstIndex = new Map([[0, -1]]);
let sum = 0, best = 0;
for (let j = 0; j < nums.length; j++) {
sum += nums[j];
if (firstIndex.has(sum - k))
best = Math.max(best, j - firstIndex.get(sum - k));
if (!firstIndex.has(sum)) firstIndex.set(sum, j);
}
return best;
}All steps as text
- The map starts with 0 → -1: the empty prefix, "just before index 0", sums to 0. A subarray ending at j sums to 3 when an earlier prefix sum equals sum - 3.
- Add nums[0] = 2: the running sum is now 2. A subarray ending here sums to 3 only if some earlier prefix summed to 2 - 3 = -1.
- No earlier prefix sums to -1, so no subarray ending at index 0 sums to 3.
- First time the running sum is 2: remember 2 → 0.
- Add nums[1] = -1: the running sum is now 1. A subarray ending here sums to 3 only if some earlier prefix summed to 1 - 3 = -2.
- No earlier prefix sums to -2, so no subarray ending at index 1 sums to 3.
- First time the running sum is 1: remember 1 → 1.
- Add nums[2] = 3: the running sum is now 4. A subarray ending here sums to 3 only if some earlier prefix summed to 4 - 3 = 1.
- 1 first appeared at index 1, so nums[2..2] sums to 4 - 1 = 3. Its length is 1: a new best.
- First time the running sum is 4: remember 4 → 2.
- Add nums[3] = -2: the running sum is now 2. A subarray ending here sums to 3 only if some earlier prefix summed to 2 - 3 = -1.
- No earlier prefix sums to -1, so no subarray ending at index 3 sums to 3.
- The sum 2 was already seen at index 0. Keep that earlier index: a start further left gives a longer subarray.
- Add nums[4] = 1: the running sum is now 3. A subarray ending here sums to 3 only if some earlier prefix summed to 3 - 3 = 0.
- 0 first appeared at index -1, so nums[0..4] sums to 3 - 0 = 3. Its length is 5: a new best.
- First time the running sum is 3: remember 3 → 4.
- Add nums[5] = 4: the running sum is now 7. A subarray ending here sums to 3 only if some earlier prefix summed to 7 - 3 = 4.
- 4 first appeared at index 2, so nums[3..5] sums to 7 - 4 = 3. Its length is 3, not longer than 5.
- First time the running sum is 7: remember 7 → 5.
- One pass, O(n). The longest subarray summing to 3 is nums[0..4], length 5.
// Template 2: prefix sum + hash map. sum(nums[i+1..j]) = P(j) - P(i), so a subarray ending at j sums to k
// exactly when an EARLIER prefix equals P(j) - k. Works with negative numbers, unlike a sliding window.
// Longest version: remember the FIRST index of each prefix sum (earliest start = longest subarray).
export function longestSubarrayWithSum(nums, k) {
const firstIndex = new Map([[0, -1]]); // the empty prefix, "before index 0"
let sum = 0;
let best = 0;
for (let j = 0; j < nums.length; j++) {
sum += nums[j];
if (firstIndex.has(sum - k)) best = Math.max(best, j - firstIndex.get(sum - k));
if (!firstIndex.has(sum)) firstIndex.set(sum, j); // never overwrite: keep the earliest
}
return best;
}Template 3: 2D prefix sums
P[r][c] sums the rectangle from the top-left corner to row r - 1, column c - 1.
Read more · 64 words
Any rectangle is four lookups: the big one, minus the strips above and left, plus the corner cut twice.
| Step | Formula |
|---|---|
| Build | P[r + 1][c + 1] = grid[r][c] + P[r][c + 1] + P[r + 1][c] - P[r][c] |
| Query rows r1..r2, columns c1..c2 | P[r2 + 1][c2 + 1] - P[r1][c2 + 1] - P[r2 + 1][c1] + P[r1][c1] |
// Template 3: 2D prefix sums. P[r][c] is the sum of the rectangle grid[0..r-1][0..c-1] (one extra row and
// column of zeros), so any rectangle sum is four lookups by inclusion-exclusion.
export function buildPrefix2D(grid) {
const rows = grid.length;
const cols = rows ? grid[0].length : 0;
const P = Array.from({ length: rows + 1 }, () => new Array(cols + 1).fill(0));
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
P[r + 1][c + 1] = grid[r][c] + P[r][c + 1] + P[r + 1][c] - P[r][c]; // the overlap was added twice
}
}
return P;
}
// Sum of grid[r1..r2][c1..c2], all ends inclusive.
export function rectSum(P, r1, c1, r2, c2) {
return P[r2 + 1][c2 + 1] - P[r1][c2 + 1] - P[r2 + 1][c1] + P[r1][c1];
}Template 4: difference arrays for range updates
A difference array runs the idea backwards.
Read more · 37 words
Adding delta to a range changes two entries: +delta at its start, -delta just after its end. One running sum rebuilds the values.
Car PoolingMedium is exactly this: the running sum is the car's load at each stop.
Difference array: many range updates, one pass
- running
- null
Instead of touching every index of every range, record only where values change. diff has one spare slot at index 6 so right + 1 never falls off the end.
function applyRangeAdds(n, updates) {
const diff = new Array(n + 1).fill(0);
for (const [left, right, delta] of updates) {
diff[left] += delta;
diff[right + 1] -= delta;
}
const out = new Array(n);
let running = 0;
for (let i = 0; i < n; i++) {
running += diff[i];
out[i] = running;
}
return out;
}All steps as text
- Instead of touching every index of every range, record only where values change. diff has one spare slot at index 6 so right + 1 never falls off the end.
- Update: add 2 to indices 1..3. The change starts at 1: diff[1] += 2.
- It stops after 3: diff[4] -= 2. Two writes, whatever the range length.
- Update: add 3 to indices 2..5. The change starts at 2: diff[2] += 3.
- It stops after 5: diff[6] -= 3. Two writes, whatever the range length.
- Update: add -1 to indices 0..1. The change starts at 0: diff[0] += (-1).
- It stops after 1: diff[2] -= (-1). Two writes, whatever the range length.
- Running sum of diff[0..0] = -1: every update whose range has started and not yet ended is counted once. out[0] = -1.
- Running sum of diff[0..1] = 1: every update whose range has started and not yet ended is counted once. out[1] = 1.
- Running sum of diff[0..2] = 5: every update whose range has started and not yet ended is counted once. out[2] = 5.
- Running sum of diff[0..3] = 5: every update whose range has started and not yet ended is counted once. out[3] = 5.
- Running sum of diff[0..4] = 3: every update whose range has started and not yet ended is counted once. out[4] = 3.
- Running sum of diff[0..5] = 3: every update whose range has started and not yet ended is counted once. out[5] = 3.
- 3 range updates cost O(1) each, and one O(n) pass built the result.
// Template 4: difference array. To add `delta` to every index in [left, right], write +delta at left and
// -delta at right + 1: O(1) per update. One running sum at the end turns the differences into values.
export function applyRangeAdds(n, updates) {
const diff = new Array(n + 1).fill(0); // one spare slot so right + 1 never falls off
for (const [left, right, delta] of updates) {
diff[left] += delta;
diff[right + 1] -= delta;
}
const out = new Array(n);
let running = 0;
for (let i = 0; i < n; i++) {
running += diff[i];
out[i] = running;
}
return out;
}Try it
// Edit the inputs and press Run. These are the four templates on this page.
const prefix = buildPrefix([3, 1, 4, 1, 5, 9]);
console.log(prefix); // [0, 3, 4, 8, 9, 14, 23]
console.log(rangeSum(prefix, 2, 4)); // 10: 4 + 1 + 5
console.log(longestSubarrayWithSum([2, -1, 3, -2, 1, 4], 3)); // 5: [2, -1, 3, -2, 1]
const P = buildPrefix2D([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
console.log(rectSum(P, 1, 1, 2, 2)); // 28: 5 + 6 + 8 + 9
console.log(applyRangeAdds(6, [[1, 3, 2], [2, 5, 3], [0, 1, -1]])); // [-1, 1, 5, 5, 3, 3]Complexity
Read more · 51 words
| Variant | Time | Extra space |
|---|---|---|
| 1D prefix sums | O(n) once, then O(1) per query | O(n) |
| Prefix sum + hash map | O(n) | O(n) |
| 2D prefix sums | O(rows × cols), then O(1) per query | O(rows × cols) |
| Difference array | O(1) per update, O(n) final pass | O(n) |
JavaScript you'll use
JavaScript refresher: Arrays: create, read, update Map and SetRead more · 37 words
Reading past either end gives undefined, and 5 - undefined is a silent NaN.
const prefix = new Array(n + 1).fill(0); // one extra slot for the empty prefix
for (let i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i];
const seen = new Map([[0, 1]]); // seed: the empty prefix, before index 0
count += seen.get(sum - k) ?? 0; // ?? 0 for "never seen"
const P = Array.from({ length: rows + 1 }, () => new Array(cols + 1).fill(0)); // 2D, rows not sharedInterview questions
The array changes between queries. Does a prefix sum still work?
Each update shifts every later prefix: O(n). For frequent updates use a Fenwick tree: O(log n) for both.
How do you count subarrays whose sum is divisible by k?
Count prefix remainders mod k (seeded 0 → 1). Two prefixes with the same remainder bound a divisible sum.
Sliding window or prefix sums: how do you choose?
Non-negative values and a longest or shortest bound: a window. Negatives, an exact sum or a count: prefix sums plus a map.
A million range increments, then point queries. What do you do?
A difference array: O(1) per update, one O(n) pass at the end.
Check yourself
Question 1 of 3Score 2 out of 3 or better to mark this page complete.
Practice problems
Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.
- Find Pivot IndexFind the leftmost index where the sum of everything before it equals the sum of everything after it.Easy
- Range Sum QueryDesign a class that answers many "sum of nums[left..right]" questions on a fixed array, each in O(1).Easy
- Subarray Sum Equals KCount the contiguous subarrays whose sum is exactly k, when the array may contain negative numbers.Medium
- Product of Array Except SelfFor every position, compute the product of all the other numbers, without using division.Medium
- Contiguous ArrayFind the longest contiguous piece of a binary array that has as many 0s as 1s.Medium
- Car PoolingDecide whether a car with limited seats can serve every trip, given where each group gets in and out.Medium
- Trapping Rain WaterGiven an elevation map of bars, compute how much rain water is trapped between them.Hard
- Maximum SubarrayFind the largest sum of a non-empty run of consecutive numbers (Kadane's algorithm).Medium