Prefix Sum Array Explained — Fast Range Queries in O(1) Time
- Prefix sum transforms an array for O(1) range sum queries after O(n) preprocessing.
- P[0]=0, P[i] = P[i-1] + arr[i-1]. Range sum(l,r) = P[r+1] - P[l].
- 2D prefix sums extend the technique to matrix rectangle queries using inclusion-exclusion.
- Build: P[0]=0, P[i] = P[i-1] + arr[i-1]. One pass, O(n).
- Query: sum(l, r) = P[r+1] — P[l]. Single subtraction, O(1).
- 2D extension: same principle on matrices for rectangle sum queries.
- Trade-off: O(n) extra space and no efficient updates. Fenwick Tree handles updates in O(log n).
- Analytics dashboards with millions of range queries per hour use prefix sums to avoid O(n) per query.
- Off-by-one on the P[0]=0 sentinel is the #1 source of wrong answers.
- Using prefix sums on arrays that change frequently. Every update requires an O(n) rebuild. Use Fenwick Tree or Segment Tree for dynamic data.
Range sum returns wrong value.
System.out.println(Arrays.toString(prefix)) — verify P[0]=0 and each P[i] = P[i-1] + arr[i-1]System.out.println("P[r+1]=" + prefix[r+1] + " P[l]=" + prefix[l] + " diff=" + (prefix[r+1]-prefix[l]))ArrayIndexOutOfBoundsException on query with r = arr.length - 1.
System.out.println("prefix.length=" + prefix.length + " arr.length=" + arr.length)Verify prefix.length == arr.length + 12D rectangle sum is wrong by a corner value.
Print each term: P[r2+1][c2+1], P[r1][c2+1], P[r2+1][c1], P[r1][c1]Verify: result = P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1]Prefix sum queries are slow despite O(1) formula.
Add timing: long start = System.nanoTime(); buildPrefix(arr); System.out.println("Build: " + (System.nanoTime()-start)/1000 + " us")Count how many times buildPrefix is called per requestProduction Incident
Production Debug GuideSymptom-first investigation path for prefix sum bugs.
Range sum queries — 'what is the sum of elements between index L and index R?' — appear everywhere: analytics dashboards aggregating revenue by time window, genomics pipelines counting nucleotide frequencies in chromosome segments, image processing computing integral images for feature detection. The naive approach loops over the range each time: O(n) per query. At millions of queries, this is a bottleneck.
Prefix sums eliminate the per-query loop. A one-time O(n) preprocessing pass builds a cumulative sum array. Every subsequent range query becomes a single subtraction: P[r+1] - P[l]. The trade-off is O(n) extra space and O(n) rebuild cost on updates — which makes prefix sums ideal for static or read-heavy datasets and unsuitable for frequently-updated arrays.
Two extensions multiply the technique's power: 2D prefix sums enable O(1) rectangle sum queries on matrices (the basis of integral images in computer vision), and difference arrays invert the pattern to support O(1) range updates with a single final read. For dynamic data requiring both queries and updates, Fenwick Trees and Segment Trees provide O(log n) for both operations at the cost of implementation complexity.
What is Prefix Sum Array? — Plain English
A Prefix Sum Array (also called a cumulative sum array) transforms an array so that range sum queries can be answered in O(1) after O(n) preprocessing. Without it, summing any subarray takes O(n). With it, any range sum takes O(1).
The prefix array P is defined as P[i] = arr[0] + arr[1] + ... + arr[i-1]. Then sum(arr[l..r]) = P[r+1] - P[l]. This works because P[r+1] includes everything up to index r, and P[l] includes everything before index l — the difference is exactly the range.
package io.thecodeforge.algo; import java.util.Arrays; public class PrefixSumArray { private final long[] prefix; /** * Builds the prefix sum array in O(n). * Uses long to prevent integer overflow on large sums. */ public PrefixSumArray(int[] arr) { prefix = new long[arr.length + 1]; for (int i = 0; i < arr.length; i++) { prefix[i + 1] = prefix[i] + arr[i]; } } /** * Returns the sum of arr[l..r] inclusive. O(1). * @throws IllegalArgumentException if l > r or indices out of bounds */ public long rangeSum(int l, int r) { if (l < 0 || r >= prefix.length - 1 || l > r) { throw new IllegalArgumentException( String.format("Invalid range [%d, %d] for array of length %d", l, r, prefix.length - 1) ); } return prefix[r + 1] - prefix[l]; } /** * Returns the total sum of the entire array. O(1). */ public long totalSum() { return prefix[prefix.length - 1] - prefix[0]; } public static void main(String[] args) { int[] arr = {3, 1, 4, 1, 5, 9, 2, 6}; PrefixSumArray ps = new PrefixSumArray(arr); System.out.println("Array: " + Arrays.toString(arr)); System.out.println("Prefix: " + Arrays.toString(ps.prefix)); System.out.println("sum(2..5) = " + ps.rangeSum(2, 5)); // 4+1+5+9 = 19 System.out.println("sum(0..7) = " + ps.rangeSum(0, 7)); // 31 System.out.println("sum(0..0) = " + ps.rangeSum(0, 0)); // 3 System.out.println("sum(7..7) = " + ps.rangeSum(7, 7)); // 6 } }
Prefix: [0, 3, 4, 8, 9, 14, 23, 25, 31]
sum(2..5) = 19
sum(0..7) = 31
sum(0..0) = 3
sum(7..7) = 6
- P[r+1] = sum of arr[0..r]. Everything up to and including r.
- P[l] = sum of arr[0..l-1]. Everything before l.
- P[r+1] - P[l] = arr[l] + arr[l+1] + ... + arr[r]. Exactly the range.
- P[0]=0 is the sentinel. Without it, l=0 requires a special case.
- The formula is always P[r+1] - P[l]. Never P[r] - P[l]. The +1 is critical.
How Prefix Sum Array Works — Step by Step
Build (O(n)): 1. Allocate prefix array P of size n+1. Set P[0] = 0. 2. For i from 1 to n: P[i] = P[i-1] + arr[i-1].
Range sum query (O(1)): 3. sum(arr[l..r]) = P[r+1] - P[l] (0-indexed).
2D prefix sum (for matrix range queries): P[i][j] = sum of rectangle arr[0..i-1][0..j-1]. Build: P[i][j] = arr[i-1][j-1] + P[i-1][j] + P[i][j-1] - P[i-1][j-1]. Query rectangle (r1,c1) to (r2,c2): P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1].
package io.thecodeforge.algo; import java.util.Arrays; public class PrefixSum2D { private final long[][] prefix; /** * Builds the 2D prefix sum array in O(mn). * P[i][j] = sum of rectangle arr[0..i-1][0..j-1]. */ public PrefixSum2D(int[][] matrix) { int rows = matrix.length; int cols = matrix[0].length; prefix = new long[rows + 1][cols + 1]; for (int i = 1; i <= rows; i++) { for (int j = 1; j <= cols; j++) { prefix[i][j] = matrix[i - 1][j - 1] + prefix[i - 1][j] + prefix[i][j - 1] - prefix[i - 1][j - 1]; } } } /** * Returns the sum of the rectangle from (r1,c1) to (r2,c2) inclusive. O(1). * Inclusion-exclusion: include bottom-right, exclude top row, exclude left column, add back corner. */ public long rectangleSum(int r1, int c1, int r2, int c2) { return prefix[r2 + 1][c2 + 1] - prefix[r1][c2 + 1] - prefix[r2 + 1][c1] + prefix[r1][c1]; } public static void main(String[] args) { int[][] matrix = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; PrefixSum2D ps = new PrefixSum2D(matrix); // Sum of entire matrix: 1+2+...+12 = 78 System.out.println("Total sum: " + ps.rectangleSum(0, 0, 2, 3)); // Sum of submatrix [1,1] to [2,2]: 6+7+10+11 = 34 System.out.println("Submatrix [1,1..2,2]: " + ps.rectangleSum(1, 1, 2, 2)); // Sum of top-left 2x2: 1+2+5+6 = 14 System.out.println("Top-left 2x2: " + ps.rectangleSum(0, 0, 1, 1)); // Single element: matrix[2][3] = 12 System.out.println("Single element [2,3]: " + ps.rectangleSum(2, 3, 2, 3)); } }
Submatrix [1,1..2,2]: 34
Top-left 2x2: 14
Single element [2,3]: 12
- P[r2+1][c2+1]: sum of rectangle from origin to (r2, c2). The big rectangle.
- P[r1][c2+1]: sum above the target rectangle. Subtract this.
- P[r2+1][c1]: sum to the left of the target rectangle. Subtract this.
- P[r1][c1]: the top-left corner, subtracted twice. Add it back once.
- Formula: P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1].
Worked Example — Tracing the Algorithm
Array: [3, 1, 4, 1, 5, 9, 2, 6]
Build prefix sum: P[0] = 0 P[1] = 0+3 = 3 P[2] = 3+1 = 4 P[3] = 4+4 = 8 P[4] = 8+1 = 9 P[5] = 9+5 = 14 P[6] = 14+9 = 23 P[7] = 23+2 = 25 P[8] = 25+6 = 31
Query sum(arr[2..5]) (indices 2 to 5, values 4+1+5+9=19): P[6] - P[2] = 23 - 4 = 19. Correct!
Query sum(arr[0..7]) (entire array = 31): P[8] - P[0] = 31 - 0 = 31. Correct!
package io.thecodeforge.algo; import java.util.Arrays; public class PrefixSumTrace { public static void main(String[] args) { int[] arr = {3, 1, 4, 1, 5, 9, 2, 6}; int n = arr.length; long[] prefix = new long[n + 1]; System.out.println("Building prefix sum array:"); System.out.println("P[0] = 0 (sentinel)"); for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + arr[i]; System.out.printf("P[%d] = P[%d] + arr[%d] = %d + %d = %d%n", i + 1, i, i, prefix[i], arr[i], prefix[i + 1]); } System.out.println("\nPrefix array: " + Arrays.toString(prefix)); // Trace queries int l = 2, r = 5; System.out.println("\nQuery sum(arr[" + l + ".." + r + "]):"); System.out.println(" P[" + (r+1) + "] - P[" + l + "] = " + prefix[r+1] + " - " + prefix[l] + " = " + (prefix[r+1] - prefix[l])); System.out.println(" Verification: " + arr[2] + "+" + arr[3] + "+" + arr[4] + "+" + arr[5] + " = " + (arr[2]+arr[3]+arr[4]+arr[5])); l = 0; r = 7; System.out.println("\nQuery sum(arr[" + l + ".." + r + "]):"); System.out.println(" P[" + (r+1) + "] - P[" + l + "] = " + prefix[r+1] + " - " + prefix[l] + " = " + (prefix[r+1] - prefix[l])); } }
P[0] = 0 (sentinel)
P[1] = P[0] + arr[0] = 0 + 3 = 3
P[2] = P[1] + arr[1] = 3 + 1 = 4
P[3] = P[2] + arr[2] = 4 + 4 = 8
P[4] = P[3] + arr[3] = 8 + 1 = 9
P[5] = P[4] + arr[4] = 9 + 5 = 14
P[6] = P[5] + arr[5] = 14 + 9 = 23
P[7] = P[6] + arr[6] = 23 + 2 = 25
P[8] = P[7] + arr[7] = 25 + 6 = 31
Prefix array: [0, 3, 4, 8, 9, 14, 23, 25, 31]
Query sum(arr[2..5]):
P[6] - P[2] = 23 - 4 = 19
Verification: 4+1+5+9 = 19
Query sum(arr[0..7]):
P[8] - P[0] = 31 - 0 = 31
- Always trace P[0]=0. If it is not 0, the sentinel is missing.
- Trace a query starting at l=0. This tests the sentinel: P[r+1] - P[0] = P[r+1].
- Trace a query in the middle. This tests the subtraction: P[r+1] - P[l].
- Trace a single-element query: l=r. Result should be arr[l].
- If any trace is wrong, the build or the formula has an off-by-one.
Difference Arrays — The Inverse of Prefix Sums
Prefix sums answer range queries in O(1) but require O(n) updates. Difference arrays invert this: they support O(1) range updates but require O(n) to read the final state. The two techniques are complementary.
A difference array D is defined as D[0] = arr[0], D[i] = arr[i] - arr[i-1]. To add value v to all elements in arr[l..r], set D[l] += v and D[r+1] -= v. To recover the final array, take the prefix sum of D.
This is ideal for problems where you perform many range updates and then read the final array once — for example, applying salary adjustments to a range of employees, then computing the final payroll.
package io.thecodeforge.algo; import java.util.Arrays; public class DifferenceArray { private final long[] diff; private final int length; /** * Initializes a difference array from the original array. * D[0] = arr[0], D[i] = arr[i] - arr[i-1]. */ public DifferenceArray(int[] arr) { length = arr.length; diff = new long[length + 1]; // +1 for the r+1 boundary diff[0] = arr[0]; for (int i = 1; i < length; i++) { diff[i] = arr[i] - arr[i - 1]; } } /** * Adds value v to all elements in arr[l..r] inclusive. O(1). */ public void rangeUpdate(int l, int r, long v) { diff[l] += v; if (r + 1 < diff.length) { diff[r + 1] -= v; } } /** * Reconstructs the final array by taking the prefix sum of the difference array. O(n). */ public long[] getResult() { long[] result = new long[length]; result[0] = diff[0]; for (int i = 1; i < length; i++) { result[i] = result[i - 1] + diff[i]; } return result; } public static void main(String[] args) { int[] salaries = {50000, 60000, 55000, 70000, 45000}; System.out.println("Original: " + Arrays.toString(salaries)); DifferenceArray da = new DifferenceArray(salaries); // Give a $5000 raise to employees 1 through 3 da.rangeUpdate(1, 3, 5000); System.out.println("After $5k raise to [1..3]: " + Arrays.toString(da.getResult())); // Apply a $2000 bonus to all employees (0 through 4) da.rangeUpdate(0, 4, 2000); System.out.println("After $2k bonus to all: " + Arrays.toString(da.getResult())); } }
After $5k raise to [1..3]: [50000, 65000, 60000, 75000, 45000]
After $2k bonus to all: [52000, 67000, 62000, 77000, 47000]
- Prefix sum: O(n) build, O(1) query, O(n) update. Best for read-heavy workloads.
- Difference array: O(1) range update, O(n) read. Best for write-heavy workloads.
- They are inverses: prefix sum of a difference array recovers the original array.
- Use prefix sums for: analytics dashboards, range sum queries on static data.
- Use difference arrays for: batch salary adjustments, range increment operations.
Fenwick Tree vs Prefix Sum — Choosing the Right Tool
Prefix sums are optimal for static data: O(n) build, O(1) query, but O(n) update. When the array changes frequently, this O(n) rebuild cost becomes prohibitive. Fenwick Trees (Binary Indexed Trees) provide O(log n) for both point updates and range queries, at the cost of more complex implementation.
The trade-off is clear: prefix sums are simpler and faster for queries (O(1) vs O(log n)), but Fenwick Trees handle dynamic data without rebuilding. Choose based on your update-to-query ratio.
package io.thecodeforge.algo; public class FenwickTree { private final long[] tree; private final int n; /** * Builds a Fenwick Tree from an array. O(n log n). * Supports point update and prefix sum query in O(log n). */ public FenwickTree(int[] arr) { n = arr.length; tree = new long[n + 1]; for (int i = 0; i < n; i++) { update(i, arr[i]); } } /** * Adds delta to arr[index]. O(log n). */ public void update(int index, long delta) { for (int i = index + 1; i <= n; i += i & (-i)) { tree[i] += delta; } } /** * Returns the sum of arr[0..index] inclusive. O(log n). */ public long prefixSum(int index) { long sum = 0; for (int i = index + 1; i > 0; i -= i & (-i)) { sum += tree[i]; } return sum; } /** * Returns the sum of arr[l..r] inclusive. O(log n). */ public long rangeSum(int l, int r) { return prefixSum(r) - (l > 0 ? prefixSum(l - 1) : 0); } public static void main(String[] args) { int[] arr = {3, 1, 4, 1, 5, 9, 2, 6}; FenwickTree ft = new FenwickTree(arr); System.out.println("sum(2..5) = " + ft.rangeSum(2, 5)); // 19 System.out.println("sum(0..7) = " + ft.rangeSum(0, 7)); // 31 // Update arr[3] from 1 to 10 (delta = +9) ft.update(3, 9); System.out.println("After update arr[3]+=9:"); System.out.println("sum(2..5) = " + ft.rangeSum(2, 5)); // 4+10+5+9 = 28 System.out.println("sum(0..7) = " + ft.rangeSum(0, 7)); // 40 } }
sum(0..7) = 31
After update arr[3]+=9:
sum(2..5) = 28
sum(0..7) = 40
| Aspect | Prefix Sum | Difference Array | Fenwick Tree (BIT) | Segment Tree |
|---|---|---|---|---|
| Build cost | O(n) | O(n) | O(n log n) | O(n) |
| Range sum query | O(1) | O(n) to read | O(log n) | O(log n) |
| Point update | O(n) rebuild | O(1) via diff | O(log n) | O(log n) |
| Range update | O(n) rebuild | O(1) | O(log n) with lazy | O(log n) with lazy |
| Space | O(n) | O(n) | O(n) | O(4n) |
| Implementation complexity | Trivial | Trivial | Moderate | Complex |
| Best for | Static data, many queries | Many range updates, one read | Dynamic data, balanced query/update | Complex queries (min, max, gcd) |
| Supports min/max query? | No | No | No (sum only) | Yes |
| Supports 2D? | Yes (integral image) | Yes | Yes (2D BIT) | Yes (2D segment tree) |
| Real-world use | Analytics dashboards, integral images | Batch range adjustments | Leaderboards, order books | Range min/max queries, interval scheduling |
🎯 Key Takeaways
- Prefix sum transforms an array for O(1) range sum queries after O(n) preprocessing.
- P[0]=0, P[i] = P[i-1] + arr[i-1]. Range sum(l,r) = P[r+1] - P[l].
- 2D prefix sums extend the technique to matrix rectangle queries using inclusion-exclusion.
- Updates require rebuilding the entire prefix array — use Fenwick Tree for dynamic updates.
- Difference arrays are the inverse: O(1) range updates, O(n) to read the final array.
- Use long for the prefix array to prevent integer overflow on large cumulative sums.
- Prefix sums + HashMap solve subarray-sum-equals-k in O(n) — a top-5 interview pattern.
- Always trace a query starting at index 0 to verify the P[0]=0 sentinel is correct.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QHow does a prefix sum enable O(1) range sum queries? Walk through the math of why P[r+1] - P[l] works.
- QWhat is the formula for a range sum query using a prefix array? Why is it P[r+1] - P[l] and not P[r] - P[l]?
- QHow would you extend prefix sums to 2D matrices? Write the build and query formulas.
- QGiven an array of integers, count the number of subarrays with sum equal to k. How do prefix sums and a HashMap solve this in O(n)?
- QExplain the trade-off between prefix sums and Fenwick Trees. When would you choose each?
- QWhat is a difference array? How does it relate to prefix sums, and when would you use it?
- QYou have an array that receives 10,000 range add operations and then one final read. What data structure do you use and why?
- QHow would you use prefix sums to compute the average of every window of size k? What is the time complexity?
- QExplain how integral images in computer vision are a 2D prefix sum. How does this enable O(1) rectangle feature computation?
- QGiven a prefix sum array, how would you find the subarray with the largest sum? What is the time complexity?
Frequently Asked Questions
Why is the prefix array size n+1 instead of n?
The extra element at index 0 (set to 0) acts as a base case that simplifies the range query formula. Without it, you would need a special case for queries starting at index 0. With P[0]=0, sum(arr[0..r]) = P[r+1] - P[0] = P[r+1], which works uniformly.
Can prefix sums handle updates to the original array?
Not efficiently. If the original array changes, you must rebuild the entire prefix array in O(n). For applications requiring both range queries and point updates, use a Fenwick Tree (Binary Indexed Tree) or Segment Tree, which support both operations in O(log n).
What is a difference array and how does it relate to prefix sums?
A difference array is the inverse of a prefix sum. It enables range updates in O(1): to add value v to all elements arr[l..r], set diff[l]+=v and diff[r+1]-=v. To read the final array, take the prefix sum of the difference array. Difference arrays are useful for bulk range-update problems where you query the final state only once.
How do prefix sums solve the 'subarray sum equals k' problem?
Compute the prefix sum P. For each index i, check if P[i] - k exists in a HashMap of previously seen prefix sums. If it does, the subarray between that index and i sums to k. This is O(n) time, O(n) space. The insight: P[i] - P[j] = k means the subarray arr[j+1..i] sums to k.
When should I use a Fenwick Tree instead of prefix sums?
Use a Fenwick Tree when the array receives frequent updates (point or range). Prefix sums require O(n) rebuild per update; Fenwick Trees support O(log n) updates and O(log n) queries. If queries vastly outnumber updates (e.g., 1M queries, 10 updates), prefix sums are simpler and faster for queries (O(1) vs O(log n)).
Can prefix sums overflow?
Yes. If the input array contains int values and the cumulative sum exceeds Integer.MAX_VALUE (2.1 billion), the prefix array overflows silently. Always use long for the prefix array. A common case: 1M elements averaging 10,000 each = 10 billion total, which overflows int.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.