Prefix Sum

Mediumarrays · prefix sum · hash map · math⏱ 5 min readUpdated

Precompute cumulative totals so any contiguous range sum is one subtraction, and combine them with a hash map to count or locate subarrays with a target sum, remainder or balance in O(n).

PatternPrefix Sum

Instinct Repeated range-sum queries, or "count subarrays whose sum equals / divides K" with negatives allowed? Store running totals and subtract two of them.

Memory hack Any range is two prefixes apart - sum(i..j) = P[j+1] - P[i], so remember every P you've passed.

At a glance

When to use it

  • Many range-sum queries are asked on an array that does not change
  • You must count subarrays whose sum equals k and negatives are allowed
  • The condition is about divisibility by k or a balance of two kinds of element
  • You need the longest subarray with a given sum or balance

Key idea

Store running totals so any range sum is the difference of two prefixes, and keep earlier prefixes in a hash map so each index finds its matching partner in O(1), turning an O(n^2) subarray scan into one O(n) pass.

Typical complexity

O(n) time, O(n) space (O(1) per range query after building)

How to spot it If the problem says "subarray sum equals k", "divisible by k" or "sum between i and j" and asks you to count, find or answer many queries, think prefix sum first.

The instinct

A prefix sum array turns a range question into a difference of two points. Define P[0] = 0 and P[i+1] = P[i] + a[i]. Then the sum of a[l..r] is P[r+1] - P[l], answered in O(1) after O(n) preprocessing.

The bigger interview idea is the prefix + hash map combo: "a subarray ending here has sum k" is the same as "some earlier prefix equals current - k". So while scanning, you ask a map how many earlier prefixes had that value. This works with negative numbers, which is exactly where sliding window breaks down.

Recognise it

Signal in the problem Why it points here
"Sum of elements between indices i and j", many queries Precompute once, answer each query with one subtraction.
"Number of subarrays whose sum equals k" (negatives allowed) Count earlier prefixes equal to prefix - k.
"Subarray sum divisible by k" / "multiple of k" Two prefixes with the same remainder bound a divisible subarray.
"Equal number of 0s and 1s", "balanced" Map 0 to -1; a balanced subarray has prefix difference 0.
"Longest subarray with sum k" Store the first index where each prefix value appears.
"Product/sum of everything except i", "left part vs right part" Prefix from the left combined with suffix from the right.
"Add v to every element in [l, r]" many times Difference array: the inverse of prefix sums.

Not this pattern if...

  • All numbers are non-negative and you want the shortest/longest window meeting a threshold: a Sliding Window uses O(1) space.
  • You want the maximum subarray sum with no target: Kadane's Running Best is simpler.
  • The array changes between queries (point updates + range queries): use a Fenwick tree or segment tree.

Memory hack

Tip

Any range is two prefixes apart. sum(i..j) = P[j+1] - P[i], so remember every P you've passed. Think of a car's odometer: to know how far you drove between two towns, you don't re-drive the road, you subtract the two odometer readings. The hash map is your logbook of past readings, and {0: 1} is the reading before you left home.

Say it out loud in the interview:

  1. "Range sum = current prefix minus an earlier prefix."
  2. "So I need an earlier prefix equal to current - target (or with the same remainder)."
  3. "Seed the map with prefix 0, query first, then record the current prefix."

How it works

For counting subarrays with sum k, scan once with a running sum. At each index, the number of subarrays ending here with sum k equals how many times sum - k appeared among earlier prefixes.

Trace for Subarray Sum Equals K on [1, 2, 3], k = 3:

i a[i] prefix need = prefix - 3 seen before count map after
start 0 0 {0:1}
0 1 1 -2 0 0 {0:1, 1:1}
1 2 3 0 1 1 {0:1, 1:1, 3:1}
2 3 6 3 1 2 {0:1, 1:1, 3:1, 6:1}

The two subarrays are [1, 2] and [3]. Notice that [1, 2] was found only because the map was seeded with {0: 1}.

Remainder trick: if P[j] % k == P[i] % k, then (P[j] - P[i]) is divisible by k. Subarray Sums Divisible by K counts equal remainders; Continuous Subarray Sum stores the first index of each remainder and checks the length is at least 2.

Balance trick: Contiguous Array replaces 0 with -1, then looks for the longest gap between two equal prefix values.

Java template

java
import java.util.HashMap;
import java.util.Map;

class PrefixSumToolkit {
    // 1) Static range queries: build once, query in O(1).
    static long[] buildPrefix(int[] a) {
        long[] p = new long[a.length + 1];               // p[0] = 0 sentinel
        for (int i = 0; i < a.length; i++) p[i + 1] = p[i] + a[i];
        return p;
    }

    static long rangeSum(long[] p, int l, int r) {       // inclusive l..r
        return p[r + 1] - p[l];
    }

    // 2) Count subarrays whose key matches (sum == k here).
    static int countSubarrays(int[] a, int k) {
        Map<Long, Integer> seen = new HashMap<>();
        seen.put(0L, 1);                                 // empty prefix
        long sum = 0;
        int count = 0;
        for (int x : a) {
            sum += x;                                    // problem-specific transform of x
            count += seen.getOrDefault(sum - k, 0);      // query first
            seen.merge(sum, 1, Integer::sum);            // then record
        }
        return count;
    }

    // 3) Longest subarray with a property: store FIRST index of each key.
    static int longestBalanced(int[] bits) {
        Map<Integer, Integer> firstAt = new HashMap<>();
        firstAt.put(0, -1);                              // prefix before index 0
        int bal = 0, best = 0;
        for (int i = 0; i < bits.length; i++) {
            bal += bits[i] == 1 ? 1 : -1;                // or ((sum % k) + k) % k for remainders
            Integer j = firstAt.putIfAbsent(bal, i);
            if (j != null) best = Math.max(best, i - j);
        }
        return best;
    }
}

Complexity

Task Time Space
Build prefix array O(n) O(n)
Each range query O(1)
Prefix + hash map counting O(n) average O(n)
Remainder version O(n) O(min(n, k))
2D prefix (matrix region sums) O(rows × cols) build, O(1) query O(rows × cols)

Variations

  • Immutable range queries: Range Sum Query - Immutable; the 2D version uses inclusion-exclusion on a matrix.
  • Count by target difference: Subarray Sum Equals K.
  • Count by equal remainder: Subarray Sums Divisible by K.
  • Existence by remainder with a length constraint: Continuous Subarray Sum (first index per remainder).
  • Longest by balance: Contiguous Array (0 becomes -1).
  • Prefix XOR / prefix parity: same idea with XOR, or with bitmasks of odd/even counts.
  • Difference array: apply many range increments with two point updates, then prefix-sum once.

Common mistakes

  • Forgetting the seed ({0: 1} for counting, {0: -1} for longest length), which misses subarrays that start at index 0.
  • Recording the current prefix before querying, which can match an empty subarray (wrong when k = 0).
  • Overwriting the first index of a key when you want the longest subarray; use putIfAbsent.
  • Integer overflow in long arrays of large values; accumulate in long.
  • Off-by-one between P[r+1] - P[l] and P[r] - P[l-1]; pick one convention and stick to it.
Warning

In Java, % keeps the sign of the dividend, so -1 % 5 == -1. With negative numbers, two prefixes that are congruent mod k can produce different keys. Normalise with ((sum % k) + k) % k before using a remainder as a map key.

All practice problems

5 problems on this site use this pattern, easiest first. Solve them in order and say the instinct out loud before you code.

My notes saved in this browser