Skip to content
Home DSA Prefix Sum Array Explained — Fast Range Queries in O(1) Time

Prefix Sum Array Explained — Fast Range Queries in O(1) Time

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Arrays & Strings → Topic 4 of 13
Prefix sum arrays let you answer range sum queries in O(1) time.
⚙️ Intermediate — basic DSA knowledge assumed
In this tutorial, you'll learn
Prefix sum arrays let you answer range sum 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.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • 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.
🚨 START HERE
Prefix Sum Triage Commands
Rapid checks to isolate prefix sum bugs in production.
🟡Range sum returns wrong value.
Immediate ActionPrint the prefix array and verify manually.
Commands
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]))
Fix NowIf P[0] is not 0, add the sentinel. If the formula uses P[r] instead of P[r+1], fix the off-by-one.
🟡ArrayIndexOutOfBoundsException on query with r = arr.length - 1.
Immediate ActionCheck prefix array size.
Commands
System.out.println("prefix.length=" + prefix.length + " arr.length=" + arr.length)
Verify prefix.length == arr.length + 1
Fix NowIf prefix.length == arr.length, reallocate with size arr.length + 1 and rebuild.
🟡2D rectangle sum is wrong by a corner value.
Immediate ActionCheck the inclusion-exclusion formula.
Commands
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]
Fix NowIf the + P[r1][c1] term is missing, add it. Without it, the top-left corner is double-subtracted.
🟠Prefix sum queries are slow despite O(1) formula.
Immediate ActionCheck if the prefix array is rebuilt on every query.
Commands
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 request
Fix NowBuild the prefix array once during data ingestion or cache initialization, not inside the query handler.
Production IncidentAnalytics Dashboard 47x Slower After Removing Prefix Sum OptimizationA SaaS analytics platform displayed revenue charts with configurable time ranges. Engineers refactored the codebase and removed the prefix sum precomputation, reasoning that 'modern hardware can handle a simple loop.' Dashboard load time spiked from 120ms to 5.6 seconds. Customer complaints flooded in within hours.
SymptomDashboard page load time increased from 120ms to 5,600ms after a code refactor. P99 latency breached the 2-second SLO. CPU utilization on the analytics service jumped from 12% to 89%. No database changes, no traffic increase, no infrastructure changes.
AssumptionA database query regression was introduced during the refactor. The team spent 6 hours profiling SQL queries, adding indexes, and checking connection pools. All database metrics were normal.
Root causeThe dashboard rendered 200 revenue charts, each requiring a range sum over a 90-day window (2,160 hourly data points). The original code precomputed a prefix sum array during data ingestion: O(n) build, O(1) per chart query. The loop over the 2,160 data points for each chart: O(n) per query. Total operations: 200 charts x 2,160 iterations = 432,000 additions per page load. With the prefix sum: 200 charts x 1 subtraction = 200 operations. The refactor increased computation by 2,160x — from 200 operations to 432,000 operations per page load.
Fix1. Restored the prefix sum precomputation during data ingestion. Build cost: O(n) once per data update. Query cost: O(1) per chart. 2. Added a unit test that asserts dashboard load completes in under 200ms with 200 charts. This test would have caught the regression. 3. Added a performance annotation: @PrefixSumOptimized on the range query method, with a comment explaining why the prefix sum is required and what happens without it. 4. Added a metric: dashboard_range_query_operations_total to track the number of addition refactor replaced this with a direct operations per page load. Alert if it exceeds 1,000.
Key Lesson
Prefix sums are not a micro-optimization. At 200 queries over 2,160 elements, the difference is 432,000 operations vs 200 operations — a 2,160x speedup.Always add a performance regression test for critical paths. The refactor passed all functional tests but broke performance silently.Document why an optimization exists. The refactor author did not know why the prefix sum was there and removed it as 'unnecessary complexity.'Prefix sums are ideal for read-heavy, static datasets. If the data changes infrequently (e.g., hourly ingestion), the O(n) rebuild cost is negligible compared to the O(1) query savings.Track operation counts, not just latency. The dashboard latency spike was a symptom; the 2,160x increase in addition operations was the root cause.
Production Debug GuideSymptom-first investigation path for prefix sum bugs.
Range sum query returns wrong value — off by one or off by the first element.Check the P[0]=0 sentinel. If missing, sum(arr[0..r]) requires special handling. Also verify the query formula: sum(l, r) = P[r+1] - P[l], not P[r] - P[l].
ArrayIndexOutOfBoundsException on range query when r = array length - 1.The prefix array has size n+1, not n. If you allocated P with size n, P[r+1] at r=n-1 accesses P[n], which is out of bounds. Allocate P with size arr.length + 1.
2D prefix sum returns wrong rectangle sum — off by a corner.The inclusion-exclusion formula must subtract P[r1][c2+1] and P[r2+1][c1] and add back P[r1][c1]. Missing the add-back double-subtracts the top-left corner. Verify: P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1].
Prefix sum returns correct values for some queries but wrong for others.Check if the original array was modified after the prefix sum was built. Prefix sums are a snapshot — any update to the original array invalidates the prefix array. Rebuild after every update.
Difference array produces wrong final values after range updates.Verify the prefix sum of the difference array is computed correctly. diff[l] += v and diff[r+1] -= v. Then the final array is the prefix sum of diff. Check that r+1 does not exceed the array bounds.
Dashboard or analytics query is unexpectedly slow despite using prefix sums.Check if the prefix sum is being rebuilt on every query instead of once during data ingestion. If the build happens inside the query handler, you pay O(n) per query instead of O(1).

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.

io/thecodeforge/algo/PrefixSumArray.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
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
    }
}
▶ Output
Array: [3, 1, 4, 1, 5, 9, 2, 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
Mental Model
Why Subtraction Gives You the Range Sum
The entire technique reduces to one insight: cumulative sums let you extract any range by subtraction, just like a cashier's running total lets you compute any time window's revenue by subtracting two readings.
  • 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.
📊 Production Insight
A genomics pipeline counted nucleotide frequencies in sliding 1000-base windows across a 3-billion-base chromosome. With 3 million windows and a naive O(n) loop per window, total operations were 3 million x 1000 = 3 billion. Using prefix sums: 3 million x 1 subtraction = 3 million operations. The pipeline runtime dropped from 45 minutes to 8 seconds. The prefix sum build cost (3 billion additions) was amortized across 3 million queries.
🎯 Key Takeaway
Prefix sums trade O(n) preprocessing and O(n) extra space for O(1) range queries. The formula is always P[r+1] - P[l]. The P[0]=0 sentinel eliminates special cases. Use long for the prefix array to prevent overflow on large sums.

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

io/thecodeforge/algo/PrefixSum2D.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
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));
    }
}
▶ Output
Total sum: 78
Submatrix [1,1..2,2]: 34
Top-left 2x2: 14
Single element [2,3]: 12
🔥The Inclusion-Exclusion Formula for 2D
  • 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].
📊 Production Insight
A computer vision pipeline computed integral images (2D prefix sums) for every frame in a 4K video stream (3840x2160 pixels). The integral image enabled O(1) rectangle sum queries for Haar-like feature detection in face recognition. Without integral images, each feature evaluation summed a rectangle of pixels — O(area) per feature. With 5,000 features per frame at 30 fps, the integral image reduced per-frame computation from ~2 billion additions to ~150,000 additions (the build cost). This enabled real-time face detection on commodity hardware.
🎯 Key Takeaway
2D prefix sums extend the 1D technique to matrices using inclusion-exclusion. The build is O(mn), the query is O(1). The +P[r1][c1] term in the query formula is mandatory — omitting it double-subtracts the top-left corner. This is the basis of integral images in computer vision.

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!

io/thecodeforge/algo/PrefixSumTrace.java · JAVA
123456789101112131415161718192021222324252627282930313233
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]));
    }
}
▶ Output
Building prefix sum array:
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
💡Pro Tip: Trace the Build and Query Manually
  • 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.
📊 Production Insight
A team implemented prefix sums for a financial reconciliation system. The build was correct but the query formula used P[r] - P[l] instead of P[r+1] - P[l]. This excluded the element at index r from every query. The bug was invisible for single-element queries (l=r, P[r] - P[l] = 0, which was obviously wrong) but silently wrong for multi-element queries. The fix: always verify with a manual trace of at least one multi-element query.
🎯 Key Takeaway
Manual tracing catches 90% of prefix sum bugs. Print the prefix array, trace a query starting at index 0 (tests the sentinel), and trace a middle query (tests the formula). The two details that cause bugs: P[0]=0 sentinel and P[r+1] not P[r].

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.

io/thecodeforge/algo/DifferenceArray.java · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
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()));
    }
}
▶ Output
Original: [50000, 60000, 55000, 70000, 45000]
After $5k raise to [1..3]: [50000, 65000, 60000, 75000, 45000]
After $2k bonus to all: [52000, 67000, 62000, 77000, 47000]
Mental Model
Prefix Sum vs Difference Array: Dual Operations
If you do 1,000 queries and 1 update: use prefix sums. If you do 1,000 updates and 1 read: use difference arrays. If you need both: use a Fenwick Tree or Segment Tree.
  • 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.
📊 Production Insight
A scheduling system applied shift adjustments (overtime pay, deductions) to ranges of employees. With 50,000 employees and 10,000 range adjustments per payroll cycle, a naive O(n) per update approach was 500 million operations. Using a difference array: 10,000 updates x O(1) = 10,000 operations, plus one O(n) reconstruction = 50,000 operations. Total: 60,000 operations vs 500 million — an 8,333x reduction.
🎯 Key Takeaway
Difference arrays are the inverse of prefix sums: O(1) range updates, O(n) final read. Use them when you have many updates and few reads. For workloads requiring both queries and updates, use a Fenwick Tree (O(log n) for both).

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.

io/thecodeforge/algo/FenwickTree.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
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
    }
}
▶ Output
sum(2..5) = 19
sum(0..7) = 31
After update arr[3]+=9:
sum(2..5) = 28
sum(0..7) = 40
⚠ Watch Out: Prefix Sum + Updates = O(n) Rebuild
If your array changes after the prefix sum is built, every update requires an O(n) rebuild. For 1,000 updates on a 1M-element array, that is 1 billion operations. Fenwick Tree does the same in 1,000 x log2(1M) = 20,000 operations — a 50,000x speedup. Use Fenwick Tree when updates are frequent.
📊 Production Insight
A real-time leaderboard maintained scores for 500,000 players. Each game round updated 10,000 player scores (point updates) and queried the top-K players (range queries) 1,000 times per second. With prefix sums, each update required a full O(n) rebuild: 10,000 updates x 500,000 = 5 billion operations per round. With a Fenwick Tree: 10,000 updates x log2(500K) ~ 19 = 190,000 operations, plus 1,000 queries x 19 = 19,000 operations. Total: 209,000 operations vs 5 billion — a 24,000x reduction. The leaderboard latency dropped from 12 seconds to 0.5 milliseconds per round.
🎯 Key Takeaway
Prefix sums are O(1) query but O(n) update. Fenwick Trees are O(log n) for both. Choose prefix sums for static data with many queries. Choose Fenwick Trees for dynamic data with frequent updates. The crossover point depends on the update-to-query ratio and array size.
🗂 Range Query and Update Techniques Compared
Choosing the right data structure for your query/update workload.
AspectPrefix SumDifference ArrayFenwick Tree (BIT)Segment Tree
Build costO(n)O(n)O(n log n)O(n)
Range sum queryO(1)O(n) to readO(log n)O(log n)
Point updateO(n) rebuildO(1) via diffO(log n)O(log n)
Range updateO(n) rebuildO(1)O(log n) with lazyO(log n) with lazy
SpaceO(n)O(n)O(n)O(4n)
Implementation complexityTrivialTrivialModerateComplex
Best forStatic data, many queriesMany range updates, one readDynamic data, balanced query/updateComplex queries (min, max, gcd)
Supports min/max query?NoNoNo (sum only)Yes
Supports 2D?Yes (integral image)YesYes (2D BIT)Yes (2D segment tree)
Real-world useAnalytics dashboards, integral imagesBatch range adjustmentsLeaderboards, order booksRange 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

    Using P[r] - P[l] instead of P[r+1] - P[l]
    Symptom

    the element at index r is excluded from every range sum —

    Fix

    always use P[r+1] - P[l]. The +1 accounts for the sentinel at P[0]. Trace a single-element query (l=r) to verify: the result should equal arr[l], not 0.

    Allocating the prefix array with size n instead of n+1
    Symptom

    ArrayIndexOutOfBoundsException when querying the full range (r = n-1, accessing P[r+1] = P[n]) —

    Fix

    array with size n instead of n+1 — Symptom: ArrayIndexOutOfBoundsException when querying the full range (r = n-1, accessing P[r+1] = P[n]) — Fix: always allocate prefix with size arr.length + 1. The extra slot is for the sentinel P[0]=0.

    Forgetting the +P[r1][c1] term in 2D prefix sum queries
    Symptom

    rectangle sum is off by the top-left corner value —

    Fix

    sum queries — Symptom: rectangle sum is off by the top-left corner value — Fix: the inclusion-exclusion formula requires adding back P[r1][c1] because it was subtracted twice (once in each strip). Verify by querying a 1x1 rectangle — the result should equal the single cell value.

    Updating the original array without rebuilding the prefix sum
    Symptom

    pre

    Fix

    sum — Symptom: prefix sum queries return stale values after the array is modified — Fix: either rebuild the prefix array after every update (O(n)) or use a Fenwick Tree for O(log n) updates. Never modify the original array and assume the prefix sum is still valid.

    Using int for the prefix array on large sums
    Symptom

    integer overflow produces negative or wrong range sums —

    Fix

    array on large sums — Symptom: integer overflow produces negative or wrong range sums — Fix: use long for the prefix array. Even if the input is int[], the cumulative sum can exceed Integer.MAX_VALUE. A common case: 1M elements averaging 10,000 = 10 billion, which overflows int.

    Using prefix sums when the array changes frequently
    Symptom

    O(n) rebuild on every update makes the system slower than the naive O(n) query approach —

    Fix

    sums when the array changes frequently — Symptom: O(n) rebuild on every update makes the system slower than the naive O(n) query approach — Fix: if updates are frequent, use a Fenwick Tree (O(log n) update and query). Prefix sums are only beneficial when queries vastly outnumber updates.

    Not handling the r+1 boundary in difference array range updates
    Symptom

    the range update bleeds past index r, affecting elements beyond the intended range —

    Fix

    check if r+1 < diff.length before setting diff[r+1] -= v. If r is the last element, skip the subtraction (the update naturally extends to the end).

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.

🔥
Naren Founder & Author

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.

← PreviousSliding Window TechniqueNext →Kadane's Algorithm
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged