Prefix and suffix techniques
Precompute running totals once, then answer any range question in O(1). Prefix sums, products and XORs, suffix arrays and difference arrays are the same idea applied in different directions.
Learning goals
- Build a prefix-sum array and answer range sums in O(1)
- Use prefix products and suffix arrays for “everything except i” questions
- Use prefix XOR for range XOR queries
- Apply many range updates with a difference array
Prerequisites
A bank statement shows a running balance. To know how much you spent between two dates you subtract two balances, instead of adding up every transaction in between.
Prefix sum
Define prefix[i] as the sum of the first i elements, so prefix[0] = 0 and prefix[i + 1] = prefix[i] + arr[i]. Building it takes one O(n) pass.
The extra leading zero is deliberate: it makes the range formula work for l = 0 without a special case. Change l and r in the animation and watch which two prefix cells are subtracted.
Worst
O(n) build, O(1) per query
Space
O(n)
Prefix sums and O(1) range queries
Interactive · edit the inputint[] arr · length 8
prefix[]
prefix has n + 1 slots. prefix[0] = 0 is the sum of nothing, which removes every special case for l = 0.
1static int[] buildPrefix(int[] arr) {2int[] prefix = new int[arr.length + 1]; // prefix[i] = arr[0] + ... + arr[i-1]3for (int i = 0; i < arr.length; i++) {4prefix[i + 1] = prefix[i] + arr[i];5}6return prefix;7}89static int rangeSum(int[] prefix, int l, int r) { // sum of arr[l..r]10return prefix[r + 1] - prefix[l];11}
Live state
n
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=8 | prefix has n + 1 slots. prefix[0] = 0 is the sum of nothing, which removes every special case for l = 0. |
| 2 | L4 | i=0 arr[i]=3 prefix[i+1]=3 | prefix[1] = prefix[0] + arr[0] = 0 + 3 = 3. |
| 3 | L4 | i=1 arr[i]=-1 prefix[i+1]=2 | prefix[2] = prefix[1] + arr[1] = 3 + -1 = 2. |
| 4 | L4 | i=2 arr[i]=4 prefix[i+1]=6 | prefix[3] = prefix[2] + arr[2] = 2 + 4 = 6. |
| 5 | L4 | i=3 arr[i]=1 prefix[i+1]=7 | prefix[4] = prefix[3] + arr[3] = 6 + 1 = 7. |
| 6 | L4 | i=4 arr[i]=5 prefix[i+1]=12 | prefix[5] = prefix[4] + arr[4] = 7 + 5 = 12. |
| 7 | L4 | i=5 arr[i]=9 prefix[i+1]=21 | prefix[6] = prefix[5] + arr[5] = 12 + 9 = 21. |
| 8 | L4 | i=6 arr[i]=-2 prefix[i+1]=19 | prefix[7] = prefix[6] + arr[6] = 21 + -2 = 19. |
| 9 | L4 | i=7 arr[i]=6 prefix[i+1]=25 | prefix[8] = prefix[7] + arr[7] = 19 + 6 = 25. |
| 10 | L10 | l=2 r=5 prefix[r+1]=21 prefix[l]=2 return=19 | Sum of arr[2..5] = prefix[6] - prefix[2] = 21 - 2 = 19. Building took O(n) once; every query afterwards is O(1). |
Prefix product
Prefix products work the same way, but division to answer range queries breaks when any value is zero. A classic application avoids division altogether: product of the array except self.
Compute, for each i, the product of everything to its left (a prefix product) and the product of everything to its right (a suffix product). Their product is the answer, in O(n) without dividing.
1static int[] productExceptSelf(int[] nums) {2int n = nums.length;3int[] out = new int[n];4out[0] = 1;5for (int i = 1; i < n; i++) out[i] = out[i - 1] * nums[i - 1]; // product of everything left of i6int right = 1;7for (int i = n - 1; i >= 0; i--) {8out[i] *= right; // times everything right of i9right *= nums[i];10}11return out;12}
Prefix XOR
XOR is its own inverse: a ^ a = 0. So with px[i + 1] = px[i] ^ arr[i], the XOR of arr[l..r] is px[r + 1] ^ px[l], exactly like the subtraction for sums.
Prefix XOR also counts subarrays whose XOR equals k: a subarray ending at i qualifies when px[i+1] ^ k appeared as an earlier prefix, which a frequency map tracks.
1static int[] buildPrefixXor(int[] arr) {2int[] px = new int[arr.length + 1];3for (int i = 0; i < arr.length; i++) px[i + 1] = px[i] ^ arr[i];4return px;5}67static int rangeXor(int[] px, int l, int r) {8return px[r + 1] ^ px[l];9}
Suffix sum
A suffix sum runs the same idea from the right: suffix[i] is the sum of arr[i..n-1]. It answers “everything from here to the end” directly and pairs with prefix sums for split-point questions.
Example: the pivot index is the first i where the sum to the left equals the sum to the right. Using a total, the right sum is total - left - arr[i], so only a running left sum is needed.
1static int pivotIndex(int[] nums) {2int total = 0;3for (int x : nums) total += x;4int left = 0;5for (int i = 0; i < nums.length; i++) {6if (left == total - left - nums[i]) return i; // right sum = total - left - nums[i]7left += nums[i];8}9return -1;10}
Difference array
A difference array stores how each element differs from the previous one: diff[i] = arr[i] - arr[i - 1]. Taking the prefix sum of diff rebuilds arr. The two operations are inverses.
That makes a range update cheap. To add v to arr[l..r], change only two entries: diff[l] += v and diff[r + 1] -= v. Enter your own updates in the animation as l, r, v triples.
Worst
O(1) per update, O(n) to rebuild
Space
O(n)
Difference array: many range updates
Interactive · edit the inputupdates (l,r,v)
diff[]
arr[]
Instead of touching every index of each range (O(n) per update), record only where each update starts and stops.
1static int[] applyUpdates(int n, int[][] updates) { // each update adds v to arr[l..r]2int[] diff = new int[n + 1];3for (int[] u : updates) {4diff[u[0]] += u[2]; // start adding v at l5diff[u[1] + 1] -= u[2]; // stop after r6}7int[] arr = new int[n];8int running = 0;9for (int i = 0; i < n; i++) {10running += diff[i];11arr[i] = running;12}13return arr;14}
Live state
n
7
updates
3
Execution trace (dry run table)Show 15 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=7 updates=3 | Instead of touching every index of each range (O(n) per update), record only where each update starts and stops. |
| 2 | L4 | l=1 r=3 v=2 | diff[1] += 2: from index 1 onward, everything is raised by 2. |
| 3 | L5 | l=1 r=3 v=2 | diff[4] -= 2: cancel the change after index 3. Each update cost O(1). |
| 4 | L4 | l=2 r=5 v=3 | diff[2] += 3: from index 2 onward, everything is raised by 3. |
| 5 | L5 | l=2 r=5 v=3 | diff[6] -= 3: cancel the change after index 5. Each update cost O(1). |
| 6 | L4 | l=0 r=1 v=-1 | diff[0] += -1: from index 0 onward, everything is lowered by 1. |
| 7 | L5 | l=0 r=1 v=-1 | diff[2] -= -1: cancel the change after index 1. Each update cost O(1). |
| 8 | L11 | i=0 diff[i]=-1 running=-1 | running += diff[0] → -1, so arr[0] = -1. A prefix sum of diff rebuilds the array. |
| 9 | L11 | i=1 diff[i]=2 running=1 | running += diff[1] → 1, so arr[1] = 1. A prefix sum of diff rebuilds the array. |
| 10 | L11 | i=2 diff[i]=4 running=5 | running += diff[2] → 5, so arr[2] = 5. A prefix sum of diff rebuilds the array. |
| 11 | L11 | i=3 diff[i]=0 running=5 | running += diff[3] → 5, so arr[3] = 5. A prefix sum of diff rebuilds the array. |
| 12 | L11 | i=4 diff[i]=-2 running=3 | running += diff[4] → 3, so arr[4] = 3. A prefix sum of diff rebuilds the array. |
| 13 | L11 | i=5 diff[i]=0 running=3 | running += diff[5] → 3, so arr[5] = 3. A prefix sum of diff rebuilds the array. |
| 14 | L11 | i=6 diff[i]=-3 running=0 | running += diff[6] → 0, so arr[6] = 0. A prefix sum of diff rebuilds the array. |
| 15 | L13 | return=[-1, 1, 5, 5, 3, 3, 0] | 3 updates applied in O(1) each, plus one O(n) pass: O(n + q) instead of O(n · q). |
Range sum
With prefix sums, the sum of arr[l..r] is prefix[r + 1] - prefix[l]. A bigger application: count subarrays whose sum equals k. A subarray (j, i] sums to k when prefix[i] - prefix[j] = k, so for each i count how many earlier prefixes equal prefix[i] - k, using a frequency map. This works with negative numbers, where a sliding window would fail.
1static int subarraySum(int[] nums, int k) {2Map<Integer, Integer> seen = new HashMap<>();3seen.put(0, 1); // the empty prefix4int prefix = 0, count = 0;5for (int x : nums) {6prefix += x;7count += seen.getOrDefault(prefix - k, 0); // earlier prefixes that leave exactly k8seen.merge(prefix, 1, Integer::sum);9}10return count;11}
Average
O(n)
Space
O(n)
Range update
With q range updates on an array of length n, updating every index directly costs O(n · q). With a difference array each update costs O(1), and one O(n) prefix pass at the end rebuilds the array: O(n + q).
This only works when all updates come before all reads. If reads and updates interleave, use a Fenwick tree or a segment tree with lazy propagation (Chapter 29).
| Workload | Best tool |
|---|---|
| Many range sums, no updates | Prefix sums: O(1) per query |
| Many range updates, read once at the end | Difference array: O(1) per update |
| Updates and queries interleaved | Fenwick tree or segment tree: O(log n) each |
Practice ladder
Now try it yourself
Easy
- Range sum query (immutable)
- Find the pivot index
- Running sum of a 1D array
Medium
- Subarray sum equals K
- Product of array except self
- Corporate flight bookings (difference array)
Advanced
- Range sum query 2D with a 2D prefix table
- Count subarrays with XOR equal to K
- Car pooling with a difference array over time
Key takeaways
- prefix[r + 1] - prefix[l] gives any range sum in O(1) after an O(n) build.
- The same trick works for XOR; products need care with zeros and overflow.
- A difference array turns each range update into two writes; a prefix pass rebuilds the result.