Skip to content

303. Range Sum Query - Immutable (Easy)

Problem

Given an integer array nums, handle multiple queries of the form sumRange(left, right), which returns the sum of elements between indices left and right inclusive. The array does not change after initialization, and sumRange may be called many times.

Example

nums = [-2, 0, 3, -5, 2, -1]
sumRange(0, 2) -> 1 (-2 + 0 + 3)
sumRange(2, 5) -> -1 (3 + -5 + 2 + -1)
sumRange(0, 5) -> -3 (-2 + 0 + 3 + -5 + 2 + -1)

LeetCode 303 · Link · Easy

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Brute force, sum on every query

Sum nums[left..right] from scratch on each query call.

class NumArray:
def __init__(self, nums: list[int]):
self.nums = nums # L1: O(n) store
def sum_range(self, left: int, right: int) -> int:
return sum(self.nums[left:right + 1]) # L2: O(n) per query

Where the time goes, line by line

Variables: n = len(nums), q = number of queries.

LinePer-call costTimes executedContribution
L1 (store)O(n)O(n)1O(n)O(n) build
L2 (sum slice)O(n)O(n)qO(qn)O(q * n) ← dominates for many queries

Complexity

  • Build: O(n)O(n)
  • Query: O(n)O(n) per call, O(qn)O(q * n) total
  • Space: O(n)O(n)

Adequate for one or two queries, but each additional query costs another O(n)O(n) pass.

final class NumArray {
private let nums: [Int]
init(_ nums: [Int]) { self.nums = nums }
func sumRange(_ left: Int, _ right: Int) -> Int { nums[left...right].reduce(0, +) }
}

Approach 2: Prefix sums (optimal)

Build a prefix array prefix of length n + 1 where prefix[i] = sum of nums[0..i-1] (i.e., prefix[0] = 0). Then:

sumRange(l, r) = prefix[r + 1] - prefix[l]

This works because prefix[r+1] is the sum of the first r+1 elements, and subtracting prefix[l] removes the first l elements, leaving exactly nums[l..r].

class NumArray:
def __init__(self, nums: list[int]):
self.prefix = [0] * (len(nums) + 1) # L1: O(n) allocate
for i, v in enumerate(nums): # L2: O(n) build prefix
self.prefix[i + 1] = self.prefix[i] + v # L3: O(1) per step
def sum_range(self, left: int, right: int) -> int:
return self.prefix[right + 1] - self.prefix[left] # L4: O(1)

Where the time goes, line by line

Variables: n = len(nums), q = number of queries.

LinePer-call costTimes executedContribution
L1 (allocate)O(n)O(n)1O(n)O(n)
L2/L3 (build prefix)O(1)O(1)nO(n)O(n) ← build dominates
L4 (query)O(1)O(1)qO(q)O(q) ← query is O(1)O(1) each

Complexity

  • Build: O(n)O(n), driven by L2/L3.
  • Query: O(1)O(1), driven by L4 (single subtraction).
  • Space: O(n)O(n) for the prefix array.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Prefix array walkthrough

nums = [-2, 0, 3, -5, 2, -1]
index 0 1 2 3 4 5
prefix[0] = 0
prefix[1] = 0 + (-2) = -2
prefix[2] = -2 + 0 = -2
prefix[3] = -2 + 3 = 1
prefix[4] = 1 + (-5) = -4
prefix[5] = -4 + 2 = -2
prefix[6] = -2 + (-1)= -3
sumRange(0, 2) = prefix[3] - prefix[0] = 1 - 0 = 1
sumRange(2, 5) = prefix[6] - prefix[2] = -3 - (-2) = -1
sumRange(0, 5) = prefix[6] - prefix[0] = -3 - 0 = -3
final class NumArray {
private var prefix: [Int] = [0]
init(_ nums: [Int]) { for value in nums { prefix.append(prefix.last! + value) } }
func sumRange(_ left: Int, _ right: Int) -> Int { prefix[right + 1] - prefix[left] }
}

Key takeaways

  • The prefix array is 1-indexed (length n + 1, first element 0) to avoid the special case left == 0.
  • The formula prefix[r+1] - prefix[l] handles any valid [l, r] including l == 0 and r == n-1.
  • Prefix sums appear in dozens of other problems: subarray sum equals k (560), range sum in a 2D matrix (304), and as a building block in many DP formulations.
  • The pattern generalizes: replace sum with XOR, product, or any associative invertible operation.

Test cases

class NumArray:
def __init__(self, nums: list[int]):
self.prefix = [0] * (len(nums) + 1)
for i, v in enumerate(nums):
self.prefix[i + 1] = self.prefix[i] + v
def sum_range(self, left: int, right: int) -> int:
return self.prefix[right + 1] - self.prefix[left]
def _run_tests():
na = NumArray([-2, 0, 3, -5, 2, -1])
assert na.sum_range(0, 2) == 1
assert na.sum_range(2, 5) == -1
assert na.sum_range(0, 5) == -3
# Single element
na2 = NumArray([5])
assert na2.sum_range(0, 0) == 5
# All negative
na3 = NumArray([-1, -2, -3])
assert na3.sum_range(0, 2) == -6
assert na3.sum_range(1, 2) == -5
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Difference Arrays, range-update tactics that mark changes at boundaries and recover final values with a prefix scan.
  • Prefix Sums, accumulation tactics for answering range-sum and subarray-count questions from differences between checkpoints.