Part 4 · Lesson 20 · 24 min read

Prefix Sum

Pre-compute running totals so that any range sum is instant.

On this page

  1. Many range sums, one pass
  2. Building the prefix array
  3. Traced: build, then answer a range query
  4. The range formula, and why the extra 0
  5. Pivot index: left sum vs right sum
  6. Prefix and suffix products
  7. Subarray sum equals k: prefix sums + a Map
  8. Practice questions (7)
  9. Make it stick
  10. What's next

Many range sums, one pass

A subarray is a block of items that sit next to each other in an array, like nums[1..3] (the items at index 1, 2 and 3). Many problems ask for the sum of many different subarrays. If you add the items up one by one, each question costs O(n) steps (the steps grow with the array length n):

JavaScript
const nums = [3, 1, 4, 1, 5];function rangeSum(l, r) {            // sum of nums[l..r], inclusive  let s = 0;  for (let i = l; i <= r; i++) s += nums[i];  return s;                          // O(r - l + 1) for every query}console.log(rangeSum(1, 3));         // 6

A query is one question, such as “what is the sum of this range?”. With 105 queries on 105 items, that is 1010 steps, which is far too slow. The fix uses the pre-computation idea from Lesson 15. Pre-computation means you do some work once, before the questions come, and save the result. Here you spend one pass building running totals. After that, every range sum is just one subtraction.

Building the prefix array

A prefix sum array is an array of running totals. In the array pre, the value at position i is the sum of the first i items of nums. A running total is the sum so far, like the total on a shop receipt that grows with each item. pre starts with a 0 (the sum of no items), so it is one item longer than nums:

nums =
3
0
1
1
4
2
1
3
5
4
↑ index (position), always starting at 0
The input.
pre =
0
0
3
1
4
2
8
3
9
4
14
5
↑ index (position), always starting at 0
pre[i] = nums[0] + … + nums[i − 1]. Each value is the previous one plus one more item. · Highlighted: pre[1] = 3 and pre[4] = 9, used by the query below.

Traced: build, then answer a range query

step 1 / 9
Set up
nums = [3, 1, 4, 1, 5]

We want any range sum to be instant.

▶const nums = [3, 1, 4, 1, 5];2const pre = [0];3for (let i = 0; i < nums.length; i++) {4  pre.push(pre[i] + nums[i]);5}6const l = 1, r = 3;7console.log(pre[r + 1] - pre[l]);
Variables
nums[3, 1, 4, 1, 5]
Console
(nothing printed yet)
O(n) once to build. After that, every range sum costs one subtraction. · interactive

The range formula, and why the extra 0

Range sum with a prefix array

sum(nums[l..r]) = pre[r + 1] - pre[l]

pre[r + 1] is the sum of everything from index 0 to r. pre[l] is the sum of everything from index 0 to l − 1. If you subtract the second from the first, the part before l is removed, and only the range l..r is left. The leading 0 lets ranges that start at index 0 work without a special case: sum(0..r) = pre[r + 1] - pre[0] = pre[r + 1] - 0.

Dry run · queries on nums = [3, 1, 4, 1, 5]
RangeFormulaAnswerCheck
0..4pre[5] − pre[0] = 14 − 0143+1+4+1+5
1..3pre[4] − pre[1] = 9 − 361+4+1
2..2pre[3] − pre[2] = 8 − 44just 4
3..4pre[5] − pre[3] = 14 − 861+5

Pivot index: left sum vs right sum

Sometimes you do not need the whole prefix array. One running total is enough. The pivot index is the position where the sum of the items on its left equals the sum of the items on its right. If you know the total of all items, the right sum is total - left - nums[i]:

JavaScript
function pivotIndex(nums) {  const total = nums.reduce((a, b) => a + b, 0);  let left = 0;                                   // sum of everything before i  for (let i = 0; i < nums.length; i++) {    const right = total - left - nums[i];          // sum of everything after i    if (left === right) return i;    left += nums[i];  }  return -1;}console.log(pivotIndex([1, 7, 3, 6, 5, 6])); // 3   (1 + 7 + 3 = 11 = 5 + 6)console.log(pivotIndex([1, 2, 3]));          // -1

Prefix and suffix products

The same idea works for multiplication, and from both directions. A prefix product is a running product, and a suffix product is a running product from the right end. Take the problem “the product of every item except nums[i]”. The answer is (the product of everything to the left) × (the product of everything to the right). A pass from the left fills in the left products. A pass from the right multiplies in the right products. You never divide, so zeros cause no problem.

JavaScript
function productExceptSelf(nums) {  const n = nums.length;  const out = new Array(n).fill(1);  let left = 1;  for (let i = 0; i < n; i++) {        // out[i] = product of everything to the left    out[i] = left;    left *= nums[i];  }  let right = 1;  for (let i = n - 1; i >= 0; i--) {   // multiply in the product of everything to the right    out[i] *= right;    right *= nums[i];  }  return out;}console.log(productExceptSelf([1, 2, 3, 4])); // [ 24, 12, 8, 6 ]
Dry run · productExceptSelf([1, 2, 3, 4])
ileft productright productout[i]
012 × 3 × 4 = 2424
113 × 4 = 1212
21 × 2 = 248
31 × 2 × 3 = 616

Subarray sum equals k: prefix sums + a Map

This is the most important prefix-sum problem: how many subarrays add up to exactly k? Checking every subarray costs O(n²). The prefix idea turns the check into a quick lookup.

A subarray from i to j has the sum pre[j + 1] - pre[i]. We want that sum to equal k. That is the same as pre[i] = pre[j + 1] - k. So as we walk forward with the running sum, we ask: how many earlier prefix sums equal (current sum − k)? A Map is a collection of key and value pairs, and it finds a key very fast. Use a Map that stores how many times each prefix sum has appeared (Lesson 15). Then each question is answered in O(1), which means one quick step.

JavaScript
function subarraySum(nums, k) {  const seen = new Map([[0, 1]]);   // prefix sum 0 has been "seen" once: before any item  let sum = 0, count = 0;  for (const x of nums) {    sum += x;                                   // prefix sum up to here    count += seen.get(sum - k) ?? 0;            // earlier prefixes that leave exactly k    seen.set(sum, (seen.get(sum) ?? 0) + 1);  }  return count;}console.log(subarraySum([1, 2, 3], 3));     // 2   ([1, 2] and [3])console.log(subarraySum([1, -1, 1], 1));    // 3   ([1], [1], [1, -1, 1])
Dry run · subarraySum([1, 2, 3], k = 3)
xsumlook for sum − 3foundcountseen after
00{0: 1}
11−200{0: 1, 1: 1}
23011{0: 1, 1: 1, 3: 1}
36312{0: 1, 1: 1, 3: 1, 6: 1}

At sum 3, the earlier prefix 0 gives [1, 2]. At sum 6, the earlier prefix 3 gives [3].

Why start the Map with 0 → 1?

A subarray that starts at index 0 needs the “empty prefix” (sum 0) to subtract. Without this first entry, the subarray [1, 2] above would be missed. This method also works with negative numbers. The sliding window of Lesson 23 does not, because it needs non-negative values.

Practice questions

For each question, decide which you need: the whole prefix array, just one running total, or prefix sums in a Map.

Q1Running sumEasy

Return the running sum of nums.

Example 1
Input: [1, 2, 3, 4]
Output: [1, 3, 6, 10]
Explanation: Each value is the sum of everything up to and including it.
Stuck? Open one hint at a time
Hint 1
Each answer is the previous answer plus the current value.
Show all 2 solutions with explanations
Approach 1Recompute each sum
The idea

For each i, add up nums[0..i] from scratch.

JavaScript
function runningSum(nums) {  return nums.map((_, i) => nums.slice(0, i + 1).reduce((a, b) => a + b, 0));}console.log(runningSum([1, 2, 3, 4])); // [ 1, 3, 6, 10 ]
How it works

This is O(n²). The slice and the sum each run a loop that you cannot see.

Approach 2Carry the total
The idea

Add the previous value to each value, in place (inside the same array).

JavaScript
function runningSum(nums) {  for (let i = 1; i < nums.length; i++) nums[i] += nums[i - 1];  return nums;}console.log(runningSum([1, 2, 3, 4]));    // [ 1, 3, 6, 10 ]console.log(runningSum([3, 1, 2, 10, 1])); // [ 3, 4, 6, 16, 17 ]
How it works

O(n) time, O(1) extra space. The result is a prefix array without the leading 0.

Which approach should you use?

The whole lesson in one line: reuse the previous total, do not work it out again. (LeetCode 1480.)

Q2Range sum queryEasy

Design a class that receives an array once. After that, it must answer many “sum of nums[left..right]” queries quickly.

Example 1
Input: nums = [-2, 0, 3, -5, 2, -1]; sumRange(0, 2), sumRange(2, 5), sumRange(0, 5)
Output: 1, -1, -3
Explanation: −2 + 0 + 3 = 1; 3 − 5 + 2 − 1 = −1; the whole array is −3.
Stuck? Open one hint at a time
Hint 1
Build the prefix array in the constructor (the setup method of a class). Then each query is one subtraction.
Show the solution with explanations
Approach 1Prefix array in a class
The idea

Pay O(n) once, answer each query in O(1).

JavaScript
class NumArray {  constructor(nums) {    this.pre = [0];    for (const x of nums) this.pre.push(this.pre[this.pre.length - 1] + x);  }  sumRange(left, right) {    return this.pre[right + 1] - this.pre[left];  }}const na = new NumArray([-2, 0, 3, -5, 2, -1]);console.log(na.sumRange(0, 2)); // 1console.log(na.sumRange(2, 5)); // -1console.log(na.sumRange(0, 5)); // -3
How it works

O(n) to build, O(1) per query, O(n) space. Negative values work in the same way.

Which approach should you use?

This is the standard prefix-sum design question. If the array could also be changed later, a prefix array would need O(n) work for each change. Then people use a different structure, such as a Fenwick tree or a segment tree. (LeetCode 303.)

Q3Find pivot indexEasy

Return the leftmost index where the sum of the values to its left equals the sum of the values to its right. If there is no such index, return −1.

Example 1
Input: [1, 7, 3, 6, 5, 6]
Output: 3
Explanation: Left of index 3: 1 + 7 + 3 = 11. Right: 5 + 6 = 11.
Example 2
Input: [1, 2, 3]
Output: -1
Explanation: No index balances.
Example 3
Input: [2, 1, -1]
Output: 0
Explanation: Edge case: the left of index 0 is empty (sum 0); the right is 1 + (−1) = 0.
Stuck? Open one hint at a time
Hint 1
right sum = total − left sum − nums[i].
Show the solution with explanations
Approach 1Total and a running left sum
The idea

Work out the total once. Then walk through the array and keep a left sum.

JavaScript
function pivotIndex(nums) {  const total = nums.reduce((a, b) => a + b, 0);  let left = 0;  for (let i = 0; i < nums.length; i++) {    if (left === total - left - nums[i]) return i;    left += nums[i];  }  return -1;}console.log(pivotIndex([1, 7, 3, 6, 5, 6])); // 3console.log(pivotIndex([1, 2, 3]));          // -1console.log(pivotIndex([2, 1, -1]));         // 0
How it works

O(n) time, O(1) space. You check before you add nums[i] to left. This keeps the pivot item itself out of both sides.

Which approach should you use?

Look at the edge case in example 3. An index can be the pivot even when one side is empty. (LeetCode 724.)

Q4Product of array except selfMedium

Return an array where each position holds the product of all the other values. Do it in O(n) time, without division.

Example 1
Input: [1, 2, 3, 4]
Output: [24, 12, 8, 6]
Explanation: For index 0: 2 × 3 × 4 = 24, and so on.
Example 2
Input: [-1, 1, 0, -3, 3]
Output: [0, 0, 9, 0, 0]
Explanation: Only index 2 (the zero) has a non-zero answer. It is the product of the other values: −1 × 1 × −3 × 3 = 9. Every other answer includes the zero, so it is 0.
Stuck? Open one hint at a time
Hint 1
Division is not allowed. It would also fail when the array has zeros.
Hint 2
out[i] = (product of everything left of i) × (product of everything right of i).
Show the solution with explanations
Approach 1Two passes with running products
The idea

The left pass stores the left product at each place. The right pass multiplies in the right product.

JavaScript
function productExceptSelf(nums) {  const n = nums.length;  const out = new Array(n);  let left = 1;  for (let i = 0; i < n; i++) { out[i] = left; left *= nums[i]; }  let right = 1;  for (let i = n - 1; i >= 0; i--) { out[i] *= right; right *= nums[i]; }  return out;}console.log(productExceptSelf([1, 2, 3, 4]));      // [ 24, 12, 8, 6 ]console.log(productExceptSelf([-1, 1, 0, -3, 3])); // [ -0, 0, 9, -0, 0 ]
How it works

O(n) time, O(1) extra space (the output array does not count). JavaScript prints -0 (“negative zero”) when a negative number is multiplied by 0. In JavaScript -0 === 0 is true, so it is still the correct answer.

Which approach should you use?

This is a classic: prefix and suffix products, with no division. (LeetCode 238.)

Q5Subarray sum equals kMedium

Return the number of subarrays whose values add up to exactly k. The values can be negative.

Example 1
Input: nums = [1, 1, 1], k = 2
Output: 2
Explanation: [1, 1] starting at index 0, and [1, 1] starting at index 1.
Example 2
Input: nums = [1, 2, 3], k = 3
Output: 2
Explanation: [1, 2] and [3].
Stuck? Open one hint at a time
Hint 1
Brute force: try every start and every end. That is O(n²).
Hint 2
A subarray that ends here has sum k exactly when an earlier prefix sum equals (current prefix − k). Count the earlier prefix sums in a Map.
Show all 2 solutions with explanations
Approach 1Every start, running sum
The idea

For each start, move the end one step at a time and keep a running sum.

JavaScript
function subarraySum(nums, k) {  let count = 0;  for (let i = 0; i < nums.length; i++) {    let s = 0;    for (let j = i; j < nums.length; j++) {      s += nums[j];      if (s === k) count++;    }  }  return count;}console.log(subarraySum([1, 1, 1], 2)); // 2
How it works

O(n²) time. It is too slow for 2 × 104 values, but it gives the right answer, so it is a good first version.

Approach 2Prefix sums in a Map
The idea

The algorithm from the lesson.

JavaScript
function subarraySum(nums, k) {  const seen = new Map([[0, 1]]);  let sum = 0, count = 0;  for (const x of nums) {    sum += x;    count += seen.get(sum - k) ?? 0;    seen.set(sum, (seen.get(sum) ?? 0) + 1);  }  return count;}console.log(subarraySum([1, 1, 1], 2)); // 2console.log(subarraySum([1, 2, 3], 3)); // 2
How it works

O(n) time, O(n) space. It works with negative numbers and zeros.

Which approach should you use?

This is one of the most important medium problems. Learn the lines inside the loop by heart. (LeetCode 560.)

Q6Longest subarray with equal 0s and 1sMedium

You get an array of 0s and 1s. Return the length of the longest subarray that has the same number of 0s and 1s.

Example 1
Input: [0, 1]
Output: 2
Explanation: One 0 and one 1.
Example 2
Input: [0, 1, 0]
Output: 2
Explanation: [0, 1] or [1, 0].
Example 3
Input: [0, 0, 1, 0, 0, 0, 1, 1]
Output: 6
Explanation: Indices 2–7: [1, 0, 0, 0, 1, 1] has three of each.
Stuck? Open one hint at a time
Hint 1
Treat each 0 as −1. Then “equal numbers of 0s and 1s” means “the sum is 0”.
Hint 2
A subarray has sum 0 when two prefix sums are equal. To get the longest one, remember the first index where each prefix sum appeared.
Show the solution with explanations
Approach 1Prefix sum → first index
The idea

Keep a running sum, and count each 0 as −1. If you saw the same sum before at index j, the part after j has sum 0.

JavaScript
function findMaxLength(nums) {  const first = new Map([[0, -1]]);    // sum 0 "seen" just before index 0  let sum = 0, best = 0;  for (let i = 0; i < nums.length; i++) {    sum += nums[i] === 1 ? 1 : -1;    if (first.has(sum)) best = Math.max(best, i - first.get(sum));    else first.set(sum, i);            // keep only the earliest index  }  return best;}console.log(findMaxLength([0, 1]));                   // 2console.log(findMaxLength([0, 1, 0]));                // 2console.log(findMaxLength([0, 0, 1, 0, 0, 0, 1, 1])); // 6
How it works
Dry run · [0, 1, 0]
ivalue as ±1sumfirst seenlength
0−1
0−1−1new → 0
1+10at −11 − (−1) = 2
2−1−1at 02 − 0 = 2
Which approach should you use?

There are two changes from Question 5. First, store the first index (not a count). Second, change the values (0 becomes −1). It takes O(n) time and O(n) space. (LeetCode 525.)

Q7Subarray sums divisible by kMedium

Return the number of non-empty subarrays whose sum is divisible by k.

Example 1
Input: nums = [4, 5, 0, -2, -3, 1], k = 5
Output: 7
Explanation: For example [5], [5, 0], [0], [-2, -3] and [4, 5, 0, -2, -3, 1] — seven in total.
Example 2
Input: nums = [5], k = 9
Output: 0
Explanation: 5 is not divisible by 9.
Stuck? Open one hint at a time
Hint 1
A subarray sum is divisible by k when two prefix sums have the same remainder after dividing by k (the remainder is also called “modulo k”).
Hint 2
Count the remainders in a Map. Remember from Lesson 13 that in JavaScript -7 % 5 is -2 (a negative remainder). So you must change it to a non-negative one.
Show the solution with explanations
Approach 1Count prefix remainders
The idea

This is like Question 5, but the Map key is the remainder sum mod k, made non-negative.

JavaScript
function subarraysDivByK(nums, k) {  const seen = new Map([[0, 1]]);  let sum = 0, count = 0;  for (const x of nums) {    sum += x;    const r = ((sum % k) + k) % k;     // always 0..k-1, even for negative sums    count += seen.get(r) ?? 0;    seen.set(r, (seen.get(r) ?? 0) + 1);  }  return count;}console.log(subarraysDivByK([4, 5, 0, -2, -3, 1], 5)); // 7console.log(subarraysDivByK([5], 9));                  // 0
How it works

O(n) time, O(k) space. Without the + k fix, the remainders −2 and 3 would count as different, but they mean the same thing when k = 5. The answer would be wrong.

Which approach should you use?

This problem joins prefix sums with modular arithmetic (working with remainders). It uses two earlier lessons together. (LeetCode 974.)

Make it stick

Make it stick — close this page, then
  1. Build the prefix array for [2, 5, 1, 3] and use it to find sum(1..2).
  2. Write the range formula and explain the leading 0.
  3. Explain product-except-self with a left pass and a right pass.
  4. Write subarray-sum-equals-k from memory, including the starting Map entry.

Revisit schedule: solve two of the questions again tomorrow, again in 3 days, and once more in a week — without looking at the answers. Each time it will feel easier. That is the memory forming.

What's next

Lesson 21 covers two pointers. You use two indices that move through the array together. They can start at both ends and move towards the middle, or they can move in the same direction. With them you can solve pair sums, 3Sum and partitioning in a single pass.

Practise on LeetCode

Real interview problems that use exactly what this lesson teaches. Solve them in JavaScript after the practice questions above.

Published by Jainik · Last updated · How these lessons are made

Keep learning

Part of the study guide

Related series