Learn/DSA/Prefix Sums
AlgorithmsBeginner7 min

Prefix Sums

Precompute running totals once so any range-sum query answers in O(1) — plus 2D grids, difference arrays, and subarray-sum tricks.

Prefix SumsArraysHash Map

A prefix sum is just a running total. Walk an array once, keeping a running sum as you go, and store each partial total. Now the sum of any contiguous slice is a single subtraction away — no re-adding required.

The intuition: if you know the total up to index r and the total up to index l, then everything in between is the difference of those two totals. You trade a one-time O(n) build for O(1) answers to every range-sum query that follows.

Array
5
0
8
1
12
2
3
3
9
4
21
5
accessed — O(1)shifting — O(n)
Access is O(1); insert/delete shift elements → O(n).
The prefix array stores running totals; a range sum arr[l..r] becomes prefix[r+1] − prefix[l] — one subtraction, no loop.

The core identity

Define prefix[0] = 0 and prefix[i+1] = prefix[i] + arr[i]. The leading zero is the trick that makes the boundary math clean: the sum of arr[l..r] inclusive is exactly prefix[r+1] − prefix[l], with no special case for l === 0.

Build once, then answer any range in O(1)
function buildPrefix(arr) {
  const prefix = [0]
  for (const n of arr) prefix.push(prefix.at(-1) + n)
  return prefix
}

const prefix = buildPrefix([3, 1, 4, 1, 5, 9])
// sum of arr[l..r] inclusive:
const rangeSum = (l, r) => prefix[r + 1] - prefix[l]
rangeSum(1, 3) // 1 + 4 + 1 = 6
OperationTime

The whole point: shift the work from query time into a single build.

2D prefix sums

The same idea lifts to a grid. Build a 2D prefix where P[i][j] is the sum of the rectangle from the top-left corner to (i, j). Any sub-rectangle sum is then four lookups combined by inclusion–exclusion — add the big block, subtract the two strips you double-counted, and add back the corner you subtracted twice.

Inclusion–exclusion for a sub-rectangle

Sum of the box with corners (r1,c1)–(r2,c2) is P[r2+1][c2+1] − P[r1][c2+1] − P[r2+1][c1] + P[r1][c1]. Draw it once and the four terms are obvious forever.

Difference arrays — the inverse trick

A difference array is prefix sums run backwards. To add a value v to every element in a range [l, r], you do diff[l] += v and diff[r+1] -= v — O(1) per range update. After all updates, take the prefix sum of diff to recover the final array. This turns many range-update problems from O(n) per update into O(1).

  • Range-sum queries on a static array — the canonical use.
  • Subarray problems — "does a subarray sum to X?" pairs prefix sums with a hash map.
  • 2D grids — image region sums, submatrix queries, and heatmaps.
  • Range updates (bookings, +v over an interval) — reach for a difference array.

Subarray sum equals K

The killer application: count subarrays whose sum equals k. A subarray arr[l..r] sums to k exactly when prefix[r+1] − prefix[l] === k, i.e. prefix[l] === prefix[r+1] − k. So as you sweep the running sum, ask a hash map how many earlier prefixes equal runningSum − k. That is an O(n) solution to a problem that looks O(n²).

Count subarrays summing to k — O(n) with a hash map
function subarraySumEqualsK(arr, k) {
  const seen = new Map([[0, 1]]) // empty prefix seen once
  let runningSum = 0
  let count = 0
  for (const n of arr) {
    runningSum += n
    count += seen.get(runningSum - k) ?? 0
    seen.set(runningSum, (seen.get(runningSum) ?? 0) + 1)
  }
  return count
}

Takeaway

Whenever a problem says "subarray" or "range" and asks about sums, your first thought should be prefix sums — and if it asks about counting ranges, pair them with a hash map.

Section navigation