Ranges show up constantly in array problems — querying the sum of a segment, updating a segment. If every operation re-scans the range from scratch, the cost stacks up linearly with the number of operations, easily going from $O(n)$ to $O(n^2)$ or worse.
Prefix sum and difference array exist to solve exactly this: spend $O(n)$ once up front, and trade away the cost of every operation that follows — querying a range sum drops to $O(1)$, updating a range also drops to $O(1)$. It’s one of the most basic trade space for time techniques — roughly speaking, seeing many repeated range-sum queries or many repeated range updates should immediately bring these two to mind.
Prefix Sum
Start with a problem (see LeetCode 303): given an integer array, and a series of queries, each query is a pair [l, r], and you need to return the sum of nums[l] through nums[r] (inclusive). For example:
1 | nums = [1, 3, 5, 7, 9, 11] |
Example queries:
query(1, 3):3 + 5 + 7 = 15query(0, 5):1 + 3 + 5 + 7 + 9 + 11 = 36query(2, 2):5 = 5
The obvious approach uses Python’s slicing:
1 | class NumArray: |
There’s a problem though: if sumRange gets called $q$ times during judging — fine if $q$ is small, but the problem’s constraints already allow up to $10^{4}$ calls — the time complexity gets pulled up to $O(n \times q)$, which very likely times out.
Break the problem down: since we already know queries will keep coming in over ranges [l, r], why not precompute the sum of the first i elements up front and store it in an array? The running total of the first i elements becomes:
1 | prefix = [0, 1, 4, 9, 16, 25, 36] |
Say we want query(1, 3), and we already know the answer is 15 — this value comes from 16 - 1, i.e. prefix[4] - prefix[1]. Writing out the general form of query(l, r):
1 | query(l, r) = prefix[r + 1] - prefix[l] |
Proof:
Proof
Let the original nums be $a$, and prefix be $p$. First write out the two prefix expressions:
Subtracting the two gives:
Which is exactly query(l, r).
$\square$
Written as a general algorithm:
1 | Algorithm 1 Build Prefix Sum |
1 | Algorithm 2 Range Sum Query |
Algorithm 1 builds the table in $O(n)$, Algorithm 2 answers each query in $O(1)$ — together they make up the complete prefix-sum playbook.
Implementation:
1 | class NumArray: |
Suffix Sum
Prefix sum handles “the sum from the start up to some position”; suffix sum flips it around — the sum from some position to the end of the array. Same pattern, opposite direction.
Suffix sum rarely shows up alone — it’s usually paired with prefix sum. A classic example: find a position (not counting itself) where the sums on either side are equal, i.e. find the pivot.
Problem (see LeetCode 724): given an integer array, find an index i such that the sum of nums[0] through nums[i - 1] equals the sum of nums[i + 1] through the end. Return -1 if no such index exists.
Brute-force approach:
1 | class Solution: |
Two sum() calls per iteration push the time complexity to $O(n^{2})$, easy to time out.
Since we’re looking for a pivot, prefix sum and suffix sum become the natural fit: build two arrays — one accumulating from the front, one from the back — then scan through once and return the first index where the two accumulated values match. Implementation:
1 | class Solution: |
Difference Array
Prefix sum handles many queries; a difference array handles the opposite situation: the same array gets many range updates — adding v to every element in [l, r] — and you only need the final result once all updates are done.
Same approach: look at the problem first. Given the following integer array:
1 | nums = [0, 0, 0, 0, 0] |
Apply these updates in order:
update(0, 2, 5): add 5 to indices0through2→[5, 5, 5, 0, 0]update(1, 3, 3): add 3 to indices1through3→[5, 8, 8, 3, 0]update(2, 4, 2): add 2 to indices2through4→[5, 8, 10, 5, 2]
Final result: [5, 8, 10, 5, 2].
Same as before, start with the obvious approach:
1 | class NumArray: |
Each update loops over r - l + 1 elements, $O(n)$ in the worst case; m updates cost $O(n \times m)$ total.
A difference array is really just an extension of prefix sum — only left and right + 1 need to change:
Proof
Let the original array be $a$. After one update(l, r, v), the new array $a^{\prime}$ is:
Define the difference array of some array $c$ as $d_{i} = c_{i} - c_{i-1}$ (with $c_{-1} = 0$ by convention). This is exactly the inverse of prefix sum — $c$ can be recovered from $d$ by taking a prefix sum:
Now check which positions change in the difference array as $a$ becomes $a^{\prime}$, i.e. as $d$ becomes $d^{\prime}$:
| Position | $a^{\prime}_{i}$ | $a^{\prime}_{i-1}$ | Result |
|---|---|---|---|
| $i < \ell$ | $a_{i}$ (unaffected) | $a_{i-1}$ (unaffected) | $d^{\prime}_{i} = d_{i}$ |
| $i = \ell$ | $a_{\ell} + v$ | $a_{\ell-1}$ (unaffected) | $d^{\prime}_{\ell} = d_{\ell} + v$ |
| $\ell < i \le r$ | $a_{i} + v$ | $a_{i-1} + v$ | $d^{\prime}_{i} = d_{i}$ (cancels) |
| $i = r + 1$ | $a_{r+1}$ (unaffected) | $a_{r} + v$ | $d^{\prime}_{r+1} = d_{r+1} - v$ |
| $i > r + 1$ | $a_{i}$ (unaffected) | $a_{i-1}$ (unaffected) | $d^{\prime}_{i} = d_{i}$ |
A whole range update only changes two positions in the difference array: $d_{\ell}$ gains $v$, $d_{r+1}$ loses $v$, everything else stays the same.
$\square$
Written as a general algorithm:
1 | Algorithm 3 Range Update via Difference Array |
1 | Algorithm 4 Reconstruct Array from Difference Array |
Algorithm 3 is $O(1)$ per update; Algorithm 4 gets called exactly once after every update is done, spending $O(n)$ to reconstruct the whole array — together they make up the complete difference-array playbook.
Implementation:
1 | class NumArray: |
2D Prefix Sum
Prefix sum only handles range sums on a 1D array. Switch to a 2D matrix and the problem becomes querying the sum of an arbitrary rectangular region, over and over. The same idea applies directly — spend $O(r \times c)$ ($r$ rows, $c$ columns) precomputing once, and every query afterward drops to $O(1)$.
Start with the problem (see LeetCode 304). Given a 2D matrix, sumRegion(row1, col1, row2, col2) gets called many times, returning the sum of all elements in the rectangle with top-left (row1, col1) and bottom-right (row2, col2).

The obvious approach is just a double loop:
1 | class NumMatrix: |
But that same double loop is exactly why it’s easy to time out — called $q$ times, the time complexity is $O(r \times c \times q)$.
Apply the same idea to two dimensions: build a prefix array the same size as matrix but with one extra row and column around the outside (same reason as prefix[0] = 0 in the 1D case — so boundary queries don’t need special-casing). Define prefix[i][j] as the sum of the rectangle from the top-left corner (0, 0) to (i - 1, j - 1).
Take the matrix below as an example:
1 | matrix = [ |
Suppose we’ve already computed:
1 | prefix[1][2] = 3 |
Building already-computed values like prefix[2][3], prefix[3][2] isn’t hard, but to build prefix[3][3], can we combine just the already-computed prefix values above and to the left? The answer: prefix[i-1][j] + prefix[i][j-1] double-counts the top-left block prefix[i-1][j-1] once, so subtract it back out once, then add the newly-included cell matrix[i-1][j-1]:
1 | prefix[i][j] = prefix[i - 1][j] + prefix[i][j - 1] |
Proof
Let the matrix be $A$, and its 2D prefix sum be $P$, defined as:
Check what range each of $P_{i-1,j} + P_{i,j-1}$ covers:
Adding the two, the top-left region $x \in [0, i-2]$, $y \in [0, j-2]$ appears in both, so it’s counted twice:
And $P_{i-1,j-1} = \sum_{x=0}^{i-2} \sum_{y=0}^{j-2} A_{x,y}$ is exactly that over-counted top-left block, so subtract it once:
This is exactly $P_{i,j} = \sum_{x=0}^{i-1} \sum_{y=0}^{j-1} A_{x,y}$ minus the last cell $A_{i-1,j-1}$, so add that back:
$\square$
With prefix built, querying uses the same inclusion-exclusion trick: sumRegion(row1, col1, row2, col2) starts from the whole large rectangle from the top-left to (row2, col2), prefix[row2+1][col2+1], subtracts the left strip prefix[row2+1][col1], subtracts the top strip prefix[row1][col2+1] — these two strips overlap once in the top-left corner (prefix[row1][col1]), subtracted twice, so add it back once:
1 | sumRegion(row1, col1, row2, col2) |
Written as a general algorithm:
1 | Algorithm 5 Build 2D Prefix Sum |
1 | Algorithm 6 2D Range Sum Query |
Algorithm 5 builds the table in $O(r \times c)$, Algorithm 6 answers each query in $O(1)$ — together they make up the complete 2D prefix-sum playbook.
Implementation:
1 | class NumMatrix: |
Solutions
LeetCode 1732: Find the Highest Altitude
Given an integer array gain of length n, where gain[i] is the net altitude change from point i to point i + 1. The starting point (point 0) has altitude 0. Return the highest altitude reached over the whole trip.
Example
1 | Input: gain = [-5,1,5,0,-7] |
A very direct application of prefix sum. Implementation:
1 | class Solution: |
There’s no real need to keep a full prefix array around, though — a single running variable, updated as you go while tracking the max, gets space down to $O(1)$:
1 | class Solution: |
LeetCode 238: Product of Array Except Self
Given an integer array nums, return an array answer where answer[i] is the product of every element in nums except nums[i], without using division, in $O(n)$ time.
Example
1 | Input: nums = [1,2,3,4] |
Same idea as finding the pivot — since it excludes itself, do one forward pass storing each position’s left-side product into result[i], then one backward pass multiplying in the right-side product. Both passes only need a single running variable (prefix, suffix) — no need to actually keep two full arrays:
1 | class Solution: |
LeetCode 1109: Corporate Flight Bookings
There are n flights, numbered 1 through n. Given a 2D array bookings, where bookings[i] = [first, last, seats] means seats seats should be reserved on every flight from first through last (inclusive). Return an array of length n giving the total number of seats reserved on each flight.
Example
1 | Input: bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5 |
Nearly identical to the difference-array example earlier — almost a direct application. Just note that since flight numbers are 1-indexed, left needs to be decremented by 1 when converting to an array index.
1 | class Solution: |