Chapter 7: Foundational Problem-Solving Patterns
7.02Beginner7 topics18 min2 interactive animations

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

ArraysLoopsFrequency maps for the subarray-sum pattern

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.

7.02.1

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 input

Your input

Try

int[] arr · length 8

3
-1
4
1
5
9
-2
6
01234567
Being comparedIn the query rangeAlready checked

prefix[]

00
·1
·2
·3
·4
·5
·6
·7
·8
Setup

prefix has n + 1 slots. prefix[0] = 0 is the sum of nothing, which removes every special case for l = 0.

Step 1 / 10
Java
1static int[] buildPrefix(int[] arr) {
2 int[] prefix = new int[arr.length + 1]; // prefix[i] = arr[0] + ... + arr[i-1]
3 for (int i = 0; i < arr.length; i++) {
4 prefix[i + 1] = prefix[i] + arr[i];
5 }
6 return prefix;
7}
8
9static int rangeSum(int[] prefix, int l, int r) { // sum of arr[l..r]
10 return prefix[r + 1] - prefix[l];
11}

Live state

0 additions

n

8

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L2n=8prefix has n + 1 slots. prefix[0] = 0 is the sum of nothing, which removes every special case for l = 0.
2L4i=0 arr[i]=3 prefix[i+1]=3prefix[1] = prefix[0] + arr[0] = 0 + 3 = 3.
3L4i=1 arr[i]=-1 prefix[i+1]=2prefix[2] = prefix[1] + arr[1] = 3 + -1 = 2.
4L4i=2 arr[i]=4 prefix[i+1]=6prefix[3] = prefix[2] + arr[2] = 2 + 4 = 6.
5L4i=3 arr[i]=1 prefix[i+1]=7prefix[4] = prefix[3] + arr[3] = 6 + 1 = 7.
6L4i=4 arr[i]=5 prefix[i+1]=12prefix[5] = prefix[4] + arr[4] = 7 + 5 = 12.
7L4i=5 arr[i]=9 prefix[i+1]=21prefix[6] = prefix[5] + arr[5] = 12 + 9 = 21.
8L4i=6 arr[i]=-2 prefix[i+1]=19prefix[7] = prefix[6] + arr[6] = 21 + -2 = 19.
9L4i=7 arr[i]=6 prefix[i+1]=25prefix[8] = prefix[7] + arr[7] = 19 + 6 = 25.
10L10l=2 r=5 prefix[r+1]=21 prefix[l]=2 return=19Sum of arr[2..5] = prefix[6] - prefix[2] = 21 - 2 = 19. Building took O(n) once; every query afterwards is O(1).
7.02.2

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.

Java
1static int[] productExceptSelf(int[] nums) {
2 int n = nums.length;
3 int[] out = new int[n];
4 out[0] = 1;
5 for (int i = 1; i < n; i++) out[i] = out[i - 1] * nums[i - 1]; // product of everything left of i
6 int right = 1;
7 for (int i = n - 1; i >= 0; i--) {
8 out[i] *= right; // times everything right of i
9 right *= nums[i];
10 }
11 return out;
12}
7.02.3

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.

Java
1static int[] buildPrefixXor(int[] arr) {
2 int[] px = new int[arr.length + 1];
3 for (int i = 0; i < arr.length; i++) px[i + 1] = px[i] ^ arr[i];
4 return px;
5}
6
7static int rangeXor(int[] px, int l, int r) {
8 return px[r + 1] ^ px[l];
9}
7.02.4

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.

Java
1static int pivotIndex(int[] nums) {
2 int total = 0;
3 for (int x : nums) total += x;
4 int left = 0;
5 for (int i = 0; i < nums.length; i++) {
6 if (left == total - left - nums[i]) return i; // right sum = total - left - nums[i]
7 left += nums[i];
8 }
9 return -1;
10}
7.02.5

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 input

Your input

Try

updates (l,r,v)

1,3,+2
2,5,+3
0,1,-1

diff[]

00
01
02
03
04
05
06
07

arr[]

·0
·1
·2
·3
·4
·5
·6
Setup

Instead of touching every index of each range (O(n) per update), record only where each update starts and stops.

Step 1 / 15
Java
1static int[] applyUpdates(int n, int[][] updates) { // each update adds v to arr[l..r]
2 int[] diff = new int[n + 1];
3 for (int[] u : updates) {
4 diff[u[0]] += u[2]; // start adding v at l
5 diff[u[1] + 1] -= u[2]; // stop after r
6 }
7 int[] arr = new int[n];
8 int running = 0;
9 for (int i = 0; i < n; i++) {
10 running += diff[i];
11 arr[i] = running;
12 }
13 return arr;
14}

Live state

0 writes

n

7

updates

3

No condition evaluated on this step
Execution trace (dry run table)Show 15 steps
StepLineStateWhat happens
1L2n=7 updates=3Instead of touching every index of each range (O(n) per update), record only where each update starts and stops.
2L4l=1 r=3 v=2diff[1] += 2: from index 1 onward, everything is raised by 2.
3L5l=1 r=3 v=2diff[4] -= 2: cancel the change after index 3. Each update cost O(1).
4L4l=2 r=5 v=3diff[2] += 3: from index 2 onward, everything is raised by 3.
5L5l=2 r=5 v=3diff[6] -= 3: cancel the change after index 5. Each update cost O(1).
6L4l=0 r=1 v=-1diff[0] += -1: from index 0 onward, everything is lowered by 1.
7L5l=0 r=1 v=-1diff[2] -= -1: cancel the change after index 1. Each update cost O(1).
8L11i=0 diff[i]=-1 running=-1running += diff[0] → -1, so arr[0] = -1. A prefix sum of diff rebuilds the array.
9L11i=1 diff[i]=2 running=1running += diff[1] → 1, so arr[1] = 1. A prefix sum of diff rebuilds the array.
10L11i=2 diff[i]=4 running=5running += diff[2] → 5, so arr[2] = 5. A prefix sum of diff rebuilds the array.
11L11i=3 diff[i]=0 running=5running += diff[3] → 5, so arr[3] = 5. A prefix sum of diff rebuilds the array.
12L11i=4 diff[i]=-2 running=3running += diff[4] → 3, so arr[4] = 3. A prefix sum of diff rebuilds the array.
13L11i=5 diff[i]=0 running=3running += diff[5] → 3, so arr[5] = 3. A prefix sum of diff rebuilds the array.
14L11i=6 diff[i]=-3 running=0running += diff[6] → 0, so arr[6] = 0. A prefix sum of diff rebuilds the array.
15L13return=[-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).
7.02.6

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.

Java
1static int subarraySum(int[] nums, int k) {
2 Map<Integer, Integer> seen = new HashMap<>();
3 seen.put(0, 1); // the empty prefix
4 int prefix = 0, count = 0;
5 for (int x : nums) {
6 prefix += x;
7 count += seen.getOrDefault(prefix - k, 0); // earlier prefixes that leave exactly k
8 seen.merge(prefix, 1, Integer::sum);
9 }
10 return count;
11}

Average

O(n)

Space

O(n)

7.02.7

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).

WorkloadBest tool
Many range sums, no updatesPrefix sums: O(1) per query
Many range updates, read once at the endDifference array: O(1) per update
Updates and queries interleavedFenwick 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.