Skip to main content
PProfileMatch

Prefix Sums and Difference Arrays in Python: Range Queries and Updates

Data Structures & AlgorithmsLesson 3 of 45 min read

What you'll learn

  • Build a prefix-sum array with the extra leading zero and answer range sums in O(1)
  • Apply many range updates with a difference array and recover final values with one pass
  • Count subarrays with a target sum by combining running totals and a hash map
  • Choose between prefix sums, a difference array and a sliding window

On this page

  1. Prefix sums: answer any range in one subtraction
  2. Difference arrays: update a whole range in O(1)
  3. Prefix sums plus a hash map: exact-sum subarrays
  4. Common mistakes
  5. How this shows up in real projects and interviews

A weather station keeps daily rainfall in millimetres, and a dashboard asks the same kind of question thousands of times: how much rain fell between day 12 and day 40? Summing that slice each time costs up to n additions per query. If you precompute running totals once, every query becomes a single subtraction. This lesson covers that trick, its mirror image for range updates, and a third use that finds subarrays with a given sum.

Prefix sums: answer any range in one subtraction

Define prefix[i] as the total of the first i values, so prefix[0] is 0. Then the total of days first through last inclusive is prefix[last + 1] - prefix[first]: everything up to and including last, minus everything before first. The extra leading zero is what removes special cases for ranges that start at day 0.

rainfall_prefix.py
from itertools import accumulate

rain_mm = [0, 4, 12, 0, 0, 7, 22, 3, 0, 1, 15, 6]      # day 0 .. day 11

# prefix[i] = total rain on days 0 .. i-1  (one extra leading 0 keeps the maths clean)
prefix = [0] + list(accumulate(rain_mm))


def rain_between(first_day, last_day):
    """Total rain from first_day to last_day, both inclusive."""
    return prefix[last_day + 1] - prefix[first_day]


print(prefix)
print("days 2-6 :", rain_between(2, 6))
print("days 0-11:", rain_between(0, 11))
print("day 6 only:", rain_between(6, 6))
assert rain_between(2, 6) == sum(rain_mm[2:7])

Output

[0, 0, 4, 16, 16, 16, 23, 45, 48, 48, 49, 64, 70]
days 2-6 : 41
days 0-11: 70
day 6 only: 22

Building the prefix array is one O(n) pass. After that every query is O(1), so q queries cost O(n + q) instead of O(n * q). The trade is memory (one extra list) and one condition: the underlying data must not change between queries. If values are updated constantly, a prefix array goes stale, and structures such as a Fenwick tree are the usual next step.

SituationTechniqueBuildEach query or update
Many range-sum questions on fixed dataPrefix sumsO(n)O(1) per query
Many range updates, read the result onceDifference arrayO(n) to read backO(1) per update
Best or shortest contiguous run, all values positiveSliding windowNoneO(n) total
Subarrays with exact sum, negatives allowedPrefix sums + hash mapNoneO(n) total

Difference arrays: update a whole range in O(1)

Now flip the problem. A conference centre has twelve hourly slots and a list of meetings, each needing one room from its first slot to its last. How many rooms are needed at the peak? Adding 1 to every slot of every meeting is a loop inside a loop. A difference array records only the changes: +1 where a booking starts and -1 just after it ends. A running total over the changes then gives the occupancy of every slot.

room_peak.py
from itertools import accumulate

HOURS = 12                                   # slots 0..11, i.e. 08:00 to 19:59

# (first_slot, last_slot) inclusive: each is one meeting needing a room
bookings = [(0, 3), (2, 5), (2, 2), (4, 8), (9, 11), (3, 4)]

diff = [0] * (HOURS + 1)                     # one spare cell so last + 1 is always valid
for first, last in bookings:
    diff[first] += 1                         # a room is taken from here on...
    diff[last + 1] -= 1                      # ...and released after `last`

in_use = list(accumulate(diff))[:HOURS]
print("diff  :", diff)
print("in use:", in_use)
print("rooms needed:", max(in_use), "| first peak slot:", in_use.index(max(in_use)))

Output

diff  : [1, 0, 2, 0, 0, -1, -1, 0, 0, 0, 0, 0, -1]
in use: [1, 1, 3, 3, 3, 2, 1, 1, 1, 1, 1, 1]
rooms needed: 3 | first peak slot: 2

Each booking costs two writes no matter how long it lasts, and one pass of accumulate rebuilds the full picture. Prefix sums and difference arrays are inverses: accumulating a difference array gives the values, and differencing the values gives back the difference array. That is why accumulate appears in both.

Prefix sums plus a hash map: exact-sum subarrays

A shop owner keeps a cashbook of daily net movement, positive and negative. How many stretches of consecutive days net to exactly 3? A stretch from day i to j sums to the target when prefix[j + 1] - prefix[i] == target, which means an earlier prefix equal to running - target must exist. Keep a dictionary of how often each prefix value has occurred, and for each day add the number of matching earlier prefixes. This works with negative values, which the sliding window cannot handle.

cashbook_stretches.py
def count_balanced_stretches(daily_net, target):
    """Count contiguous day ranges whose net cash movement equals `target`."""
    seen = {0: 1}                            # the empty prefix has sum 0, seen once
    running = 0
    found = 0
    for amount in daily_net:
        running += amount
        found += seen.get(running - target, 0)   # earlier prefixes that leave exactly `target`
        seen[running] = seen.get(running, 0) + 1
    return found


cashbook = [3, -1, 2, 1, -3, 4, 0, -2]
print(count_balanced_stretches(cashbook, 3))


def brute(daily_net, target):
    n = len(daily_net)
    return sum(1 for i in range(n) for j in range(i, n) if sum(daily_net[i:j + 1]) == target)


print(brute(cashbook, 3))

Output

4
4

The four stretches are [3], [2, 1], [-1, 2, 1, -3, 4] and [-1, 2, 1, -3, 4, 0]. Note that the last two differ only by a trailing zero, and that the first one starts at day 0, which is exactly why the dictionary begins with {0: 1}. The brute-force version is O(n^3) as written and the fast one is O(n). Writing a slow, obviously correct version beside your clever one is the best way to trust it.

A quick way to decide between these tools is to look at what changes. If the data is fixed and the questions vary, precompute once and answer each question from the table. If the questions are fixed (apply these updates) and you only need the end state, record the changes cheaply and resolve them in one sweep. If you need the best run of a certain shape and values are positive, slide a window. The three techniques share the same skeleton of one pass and a little bookkeeping; they differ in what that bookkeeping remembers.

Common mistakes

  • Skipping the leading zero in the prefix array. Symptom: queries starting at index 0 need a special case, and forgetting it raises IndexError or returns a total that is short by the first value.
  • Using `prefix[last] - prefix[first]` for an inclusive range. Symptom: every answer is missing the last day's value.
  • No spare cell in the difference array. Symptom: IndexError: list index out of range the first time a booking ends in the final slot.
  • Forgetting `{0: 1}` in the hash-map version. Symptom: stretches that start at day 0 are not counted; on the cashbook above the result is 3 instead of 4.
  • Reusing a prefix array after the data changed. Symptom: queries return old totals with no error. Rebuild it, or use a structure designed for updates.

How this shows up in real projects and interviews

Prefix sums sit behind analytics dashboards that sum a metric over a date range, cumulative histograms and image processing (a 2D version gives the sum of any rectangle). Difference arrays appear in schedulers, booking systems and "apply +x to this range" batch jobs. In interviews, "subarray sum equals k", "range sum query", "corporate flight bookings" and "meeting rooms" are all variants. Good answers name the technique, give both build and query costs, and mention what breaks it (frequent updates). When the whole question is about the minimum or maximum over a window rather than a sum, return to the sliding window lesson.

Try it yourself

Given hourly counts of visitors entering a museum, write quiet_windows(counts, k, limit) that returns every start index where the total of k consecutive hours is at most limit, using a prefix array. Then, using a difference array, apply a list of promotions that each add a discount of d to every product price index from a to b, and print the final discount per product.

Show a hint

For the first part, the window starting at i has total prefix[i + k] - prefix[i]. For the second, add d at a and subtract d at b + 1, then accumulate.

Key takeaways

  • prefix[i] holds the total of the first i values; the sum of [first, last] is prefix[last + 1] - prefix[first].
  • Prefix sums make range-sum queries O(1) after an O(n) build, but only while the data stays unchanged.
  • A difference array turns each range update into two writes; accumulate recovers the final values.
  • Prefix sums plus a hash map count exact-sum subarrays in O(n), even with negative values.
  • Use half-open ranges consistently to avoid off-by-one errors.

Test yourself

A trick from this lesson's ride: prefix[i] is the total of the first i values, so end is exclusive. Four more tricks like it wait along the route, each followed by a question.

Ride this lesson

Frequently asked questions

What is a prefix sum array?

A prefix sum array stores running totals, so prefix[i] is the sum of the first i elements. Once built in O(n), the sum of any contiguous range is the difference of two prefix values, giving O(1) range-sum queries. It is only valid while the underlying values do not change.

What is a difference array used for?

A difference array makes many range updates cheap. To add a value to every position from a to b you change just two cells, add at a and subtract at b + 1, then take running totals once at the end to get the final array. It is the inverse of a prefix sum.

How do I count subarrays with sum equal to k in Python?

Keep a running prefix total and a dict counting how often each prefix value has occurred, starting with {0: 1}. For each element, add seen.get(running - k, 0) to the answer, then record the current total. This runs in O(n) and works with negative numbers.