Prefix sums
Store running totals once, so the sum of any range of an array takes one subtraction.
O(n) to build, O(1) per rangeSpace O(n)15 practice problemsStep-through pictureThe idea
Suppose you need the sum of many different ranges of the same array: the sales from day 3 to day 10, then from day 5 to day 6, and so on. Adding up each range separately takes as many steps as the range is long.
A prefix sum array stores running totals: prefix[i] is the sum of the first i numbers. Building it takes one pass. After that, the sum of any range from left to right is prefix[right + 1] - prefix[left]: the total up to the end of the range, minus the total before it starts.
prefix has one more slot than nums, and prefix[0] is 0, the sum of no numbers. That extra slot means ranges that start at index 0 need no special case.
An everyday example
Reading a car's odometer
A car's odometer shows the total distance it has ever driven. To know how far you drove today, you don't add up every street you took. You subtract this morning's reading from tonight's. Any trip's length is the reading at its end minus the reading at its start, and that works for any trip, however long.
| In the example | In the code |
|---|---|
| The odometer reading at each moment | prefix[i], the total of the first i numbers |
| Driving one more street | prefix[i + 1] = prefix[i] + nums[i] |
| The reading before the trip starts | prefix[left] |
| The trip's length | prefix[right + 1] - prefix[left] |
From the slow way to prefix sums
- The obvious way to answer "what is the sum from
lefttoright?" is a loop that adds those numbers. A range can bennumbers long, soqquestions cost up ton × qadditions. For 1,000 questions on 100,000 numbers, that is 100 million additions. - Much of that work repeats. Two overlapping ranges add the same middle numbers again, and a range that starts at index 0 redoes everything an earlier range from 0 already added. The answers share parts, but the loop throws each part away.
- Every range sum is a difference of two sums that start at index 0: the sum up to
right, minus the sum of the part beforeleft. So onlyn + 1numbers are needed in total, the sums of the first 0, 1, 2, and up tonnumbers. - Each of those sums is the one before it plus one more number, so a single pass builds them all. Put
0in front for the sum of no numbers, and every range becomes one subtraction. Build once, then subtract for each question: that loop is prefix sums.
How it works
- Create
prefixwithnums.length + 1zeros.prefix[0]stays 0. - Walk
ifrom 0 to the end:prefix[i + 1] = prefix[i] + nums[i]. - Now
prefix[i]is the sum ofnums[0]throughnums[i - 1]. - To get the sum from
lefttoright(both included), takeprefix[right + 1] - prefix[left]. - Each range question is now one subtraction, whatever its length.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function buildPrefix(nums) {const prefix = new Array(nums.length + 1).fill(0);for (let i = 0; i < nums.length; i++) {prefix[i + 1] = prefix[i] + nums[i];}return prefix;}function rangeSum(prefix, left, right) {return prefix[right + 1] - prefix[left];}
How fast it is
| Time | O(n) to build, O(1) per range |
|---|---|
| Extra memory | O(n) |
Building visits each number once. Each range sum is one subtraction of two stored totals. The prefix array has n + 1 numbers.
- Best case
O(1)Answering a question once
prefixexists: one subtraction, whether the range has 1 number or 100,000. - Average case
O(n + q)Building once in
nsteps and then answeringqquestions in one step each. - Worst case
O(n)A single question on a fresh array. Building costs
nsteps, so for one question prefix sums are no faster than adding the range directly.
Common mistakes
- Mixing up the shift: the range
left..rightisprefix[right + 1] - prefix[left], notprefix[right] - prefix[left]. - Making
prefixthe same length asnums, which forces special cases for ranges that start at 0. - Rebuilding the prefix array for every question, which throws away the whole benefit.
- Using prefix sums when the array changes between questions. Each change would mean rebuilding; use a Fenwick tree instead.
When to use it
Look for these clues in a problem:
- Many questions ask for the sum (or count) of a range
left..rightof the same array. - The problem mentions running totals, cumulative values, or a balance over time.
- You need the sum of everything to the left and to the right of each index.
- The array doesn't change between questions (if it does, look at a Fenwick tree).
Check yourself
Answer each one in your head first, then open it.
For nums = [4, -2, 7, 0, 3], what is rangeSum(prefix, 2, 4)?
prefix[5] - prefix[2] = 12 - 2 = 10, which matches 7 + 0 + 3.
Why can't prefix maximums answer "largest number from left to right"?
Subtraction undoes a sum, but nothing undoes a maximum. Knowing the largest of the first 5 numbers and of the first 2 doesn't tell you the largest of numbers 3 to 5.
How many additions does building prefix take for 1,000 numbers, and how many steps for 500 questions?
1,000 additions to build, then 500 subtractions, one per question.
When all numbers are non-negative, what extra property does prefix have?
It never goes down, so it is sorted. That lets you binary search it, for example for the first position where the running total reaches a target.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 15 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingeasy
medium
Go further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The build keeps one rule: after the loop step for index i, prefix[i + 1] equals nums[0] + ... + nums[i]. Before the loop, prefix[0] is 0, the sum of no numbers. Each step adds exactly one more number to the total before it, so the rule holds for every slot when the loop ends.
For any left <= right, prefix[right + 1] adds up nums[0..right], and prefix[left] adds up nums[0..left - 1], which is the first part of the same list. Subtracting removes exactly that first part and leaves nums[left..right]. Only adding and subtracting are used, so negatives and zeros need no care.
The trick needs two things. The array must not change after prefix is built, or the stored totals go stale. And the operation must be undoable: you can take a sum away with subtraction, or an XOR away with another XOR. Taking a maximum can't be undone, so prefix maximums can't answer "largest number from left to right".
Going deeper
4 more ideas
Why the subtraction gives the range
prefix[right + 1] is nums[0] + ... + nums[right]. prefix[left] is nums[0] + ... + nums[left - 1]. Subtracting cancels every number before left, leaving exactly nums[left] + ... + nums[right].
Why the extra zero at the front
Without prefix[0] = 0, a range starting at index 0 would need prefix[left - 1], which is prefix[-1], so you'd need an if. Shifting everything by one slot makes every range use the same formula. It's the most common source of off-by-one bugs, so keep the rule in mind: prefix is one longer than nums.
Variations
The same trick works for anything you can add and take away: counts (how many errors happened between two times), running products (careful with zeros), and XOR. Combined with a hash map, it finds ranges with a given sum. In two dimensions it gives the sum of any rectangle.
Cost
Building takes O(n) time and O(n) space. Each range question then takes O(1). With q questions, the total is O(n + q) instead of O(n * q) for adding up each range.
Work it by hand
Every step in a table, with why
Build the running totals of five numbers, including a negative and a zero, then answer two range questions.
nums = [4, -2, 7, 0, 3]
| Step | Step | Reads | Writes | prefix after | Why |
|---|---|---|---|---|---|
| 1 | i = 0 | prefix[0] = 0, nums[0] = 4 | prefix[1] = 4 | [0, 4, 0, 0, 0, 0] | prefix starts as six zeros, one more slot than nums. The first total is 0 + 4. |
| 2 | i = 1 | prefix[1] = 4, nums[1] = -2 | prefix[2] = 2 | [0, 4, 2, 0, 0, 0] | A negative number makes the running total go down. Nothing special is needed. |
| 3 | i = 2 | prefix[2] = 2, nums[2] = 7 | prefix[3] = 9 | [0, 4, 2, 9, 0, 0] | prefix[3] is the sum of the first three numbers: 4 - 2 + 7 = 9. |
| 4 | i = 3 | prefix[3] = 9, nums[3] = 0 | prefix[4] = 9 | [0, 4, 2, 9, 9, 0] | A zero leaves the total unchanged, so two neighboring totals are equal. |
| 5 | i = 4 | prefix[4] = 9, nums[4] = 3 | prefix[5] = 12 | [0, 4, 2, 9, 9, 12] | The last slot holds the sum of the whole array, 12. Building is done after 5 additions. |
| 6 | rangeSum(prefix, 1, 3) | prefix[4] = 9, prefix[1] = 4 | returns 9 - 4 = 5 | unchanged | The total up to index 3, minus the total before index 1, leaves -2 + 7 + 0 = 5. |
| 7 | rangeSum(prefix, 0, 4) | prefix[5] = 12, prefix[0] = 0 | returns 12 - 0 = 12 | unchanged | A range starting at index 0 subtracts prefix[0], which is 0. No special case is needed. |
prefix is [0, 4, 2, 9, 9, 12], built with 5 additions. Each question then costs one subtraction, however long its range: 5 for indexes 1 to 3 and 12 for the whole array.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Prefix sums | O(n + q) | O(n) | The array doesn't change and you'll ask many range questions about it. |
| Add up each range | O(n · q) | O(1) | Only one or two questions, or the array changes all the time. |
| Fenwick tree | O(q log n) | O(n) | Values change between questions, so each update and each sum must be fast. |
Extra memory: O(n): prefix holds n + 1 numbers. You could write the totals into nums itself to save memory, but that destroys the input.
How the steps grow
- Prefix sums
O(n + q) - Add up each range
O(n · q) - Fenwick tree
O(q log n)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Prefix sums | 20 | 2,000 | 2 million |
| Add up each range | 100 | 1 million | 1 trillion |
| Fenwick tree | 33 | 9,966 | 20 million |
Inputs that need care
5 tricky inputs
- An empty array:
nums = [] prefixis[0]. No range withleft <= rightexists, so there is nothing to ask.rangeSum(prefix, 0, -1)returns 0, the sum of an empty range.- A range of one number:
left = right = 2 prefix[3] - prefix[2]is9 - 2 = 7, which is justnums[2].left > right:rangeSum(prefix, 3, 1)- It returns
prefix[2] - prefix[3] = 2 - 9 = -7, a meaningless number. Check the order, or swap the two indexes, before calling. - An index past the end:
right = nums.length prefix[right + 1]isundefined, and the subtraction givesNaN. Check bounds at the edge of your code.- Very large totals, past
2^53 - JavaScript numbers lose exact whole-number precision there, so totals can be off. Use
BigIntfor the totals when values are that large.
Variations and follow-ups
Twists interviewers add
Product of everything except yourself
Build running products from the left and from the right. The answer at i is the product of everything before i times the product of everything after it. This avoids dividing, which breaks when a number is zero.
Balance point
To find an index where the sum on its left equals the sum on its right, compare prefix[i] with prefix[n] - prefix[i + 1] for each i. One build and one pass answer it in O(n).
Values that change: a Fenwick tree
If numbers change between questions, each change would force a rebuild. A Fenwick tree stores partial totals so that both a change and a range sum take O(log n) steps.
Weighted random pick
With non-negative weights, prefix only goes up, so it is sorted. Draw a random number below the total and binary search prefix for the first total above it. Heavier items cover wider stretches, so they're picked more often.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "sum of the subarray from
itoj", "many range queries", or a hint that the array never changes. - Say the definition out loud: "
prefix[i]is the sum of the firstinumbers, so the range isprefix[right + 1] - prefix[left]." Saying "firstinumbers" settles the off-by-one question. - Test three ranges by hand: one starting at index 0, one of a single number, and the whole array.
- The usual follow-up is "what if numbers can change?". Answer: a Fenwick tree or a segment tree, with
O(log n)per update and per query. Another is "and in two dimensions?": 2D prefix sums.
Where frontend code uses it
The same idea in real UI code
- Virtualized lists with rows of different heights keep running totals of the heights, so the top position of row
iisprefix[i]. - Code editors keep the character offset where each line starts, which is a prefix sum of line lengths, to turn an offset into a line and column.
- Charts that show cumulative totals (revenue to date, total sign-ups) plot a prefix sum of the daily numbers.