Data Structures & Algorithms (DSA) Module 2 – Arrays, strings and in-place techniques
Prefix sums and difference arrays
Answer any range total in O(1) after one pass, apply many range updates in O(n + q) with a difference array, and know when each one fits.
What you will learn
- Answer range-sum queries in O(1) after O(n) preprocessing
- Apply many range increments in O(n + q) with a difference array
- Extend prefix sums and difference arrays to two-dimensional grids
- Choose a type for prefix totals from the largest total they can reach
Before you start
What you’ll learn · Before you start
What you will learn
- Answer range-sum queries in O(1) after O(n) preprocessing
- Apply many range increments in O(n + q) with a difference array
- Extend prefix sums and difference arrays to two-dimensional grids
- Choose a type for prefix totals from the largest total they can reach
Before you start
On this page
A prefix sum array stores running totals: prefix[0] = 0, and prefix[i] is the total of the first i items of an
array. Building it takes one pass, O(n). After that, the total of any range a[first..last] is one subtraction,
prefix[last + 1] − prefix[first], because both prefixes contain the items before first and they cancel. A
difference array runs the idea backwards. To add v to every item of a range, record +v where the range starts and −v
just after it ends; once all the updates are recorded, one running total turns the marks into the final values, so q
range updates cost O(n + q) instead of up to n × q. Prefix sums suit many range questions about data that does not
change; difference arrays suit many range updates made before anyone reads the values.
Range totals in one subtraction
A year of daily rainfall makes a good test: 365 numbers and endless questions such as “how much fell between day 151 and day 242?”. Adding up the range each time costs up to 365 additions per question. With prefix sums the year is added up once, and every question after that is answered by one subtraction:
"""Rainfall totals for any range of days, from a prefix array of a year of synthetic daily readings.
prefix[i] is the total of the first i days, with prefix[0] = 0, so the total of days first to last (counting from 0,
both included) is prefix[last + 1] - prefix[first]. The program also counts the additions each method needs.
"""
import random
rng = random.Random(365)
rain_mm = [rng.choice([0, 0, 0, 1, 2, 4, 7, 12, 25, 40]) for _ in range(365)] # synthetic daily rainfall in mm
prefix = [0] * (len(rain_mm) + 1)
for i, mm in enumerate(rain_mm):
prefix[i + 1] = prefix[i] + mm
print("first ten days:", rain_mm[:10])
print("prefix[0..10]: ", prefix[:11])
queries = [(0, 364), (151, 242), (10, 10), (300, 330)]
for first, last in queries:
total = prefix[last + 1] - prefix[first]
assert total == sum(rain_mm[first : last + 1])
print(f"days {first:>3} to {last:>3}: {total:>5} mm = prefix[{last + 1}] - prefix[{first}]")
q = 100_000
print(f"{q:,} random range queries: up to {q * 365:,} additions one by one, {365 + q:,} operations with the prefix array") Output
first ten days: [2, 0, 2, 40, 1, 25, 7, 40, 7, 25] prefix[0..10]: [0, 2, 2, 4, 44, 45, 70, 77, 117, 124, 149] days 0 to 364: 3065 mm = prefix[365] - prefix[0] days 151 to 242: 668 mm = prefix[243] - prefix[151] days 10 to 10: 0 mm = prefix[11] - prefix[10] days 300 to 330: 266 mm = prefix[331] - prefix[300] 100,000 random range queries: up to 36,500,000 additions one by one, 100,365 operations with the prefix array
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rainfall.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Each answer is checked against adding up the range directly. The last line puts the saving in numbers: 100,000
questions would cost up to 36.5 million additions one by one, against 100,365 operations with the prefix array.
Python can build the array in one call, list(itertools.accumulate(rain_mm, initial=0)); the loop shows what that call
does.
The zero in front and half-open ranges
The leading 0 is what keeps the formula free of special cases. With it, prefix[i] is the total of the half-open
range a[0:i], the first i items, and a range that starts at day 0 is simply prefix[last + 1] − prefix[0].
Without it, the array would hold the totals of a[0..i], and a range starting at 0 would need prefix[−1], which
Python does not reject: it quietly reads the last entry and returns a wrong answer. Thinking in half-open ranges,
start included and end excluded, removes the other classic slip: the total of a[first..last] uses last + 1.
Many range updates with a difference array
Now turn the problem round. A hall has 8 seats, bookings reserve ranges of seats, and at the end you need the number of bookings covering each seat. Adding 1 to every seat of every booking costs as much as the bookings are long. Difference arrays record each booking with two marks instead:
Two marks per booking, then one running total
Text description of the diagram
The diagram shows how a difference array records two bookings on eight seats, from top to bottom.
- The bookings reserve seats 1 to 4 and seats 3 to 6.
- Each booking leaves two marks: +1 at its first seat and −1 at the seat just after its last one. The marks are +1 at seat 1, +1 at seat 3, −1 at seat 5 and −1 at seat 7; every other seat has 0. Recording a booking takes two additions, however many seats it covers.
- A running total of the marks, from seat 0 to seat 7, gives the number of bookings that cover each seat: 0, 1, 1, 2, 2, 1, 1 and 0. Seats 3 and 4 are in both bookings. That one pass takes O(n) time for n seats, so q bookings cost O(n + q) in all.
"""Applies range bookings to seats with a difference array, then checks the result against doing them one by one.
A booking (first, last) adds 1 to every seat from first to last. The difference array records only its two ends:
+1 at first and -1 just after last. One prefix sum over the marks then gives every seat's count.
"""
SEATS = 8
bookings = [(1, 4), (3, 6), (0, 7), (5, 5)]
diff = [0] * (SEATS + 1) # one extra slot, so that last + 1 is always a valid index
for first, last in bookings:
diff[first] += 1
diff[last + 1] -= 1
print(f"booking seats {first} to {last}: marks {diff[:SEATS]} (and {diff[SEATS]} past the end)")
count, running = [], 0
for i in range(SEATS):
running += diff[i]
count.append(running)
print("prefix sum of the marks:", count)
one_by_one = [0] * SEATS
for first, last in bookings:
for seat in range(first, last + 1):
one_by_one[seat] += 1
print("the same, seat by seat: ", one_by_one) Output
booking seats 1 to 4: marks [0, 1, 0, 0, 0, -1, 0, 0] (and 0 past the end) booking seats 3 to 6: marks [0, 1, 0, 1, 0, -1, 0, -1] (and 0 past the end) booking seats 0 to 7: marks [1, 1, 0, 1, 0, -1, 0, -1] (and -1 past the end) booking seats 5 to 5: marks [1, 1, 0, 1, 0, 0, -1, -1] (and -1 past the end) prefix sum of the marks: [1, 2, 2, 3, 3, 3, 2, 1] the same, seat by seat: [1, 2, 2, 3, 3, 3, 2, 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bookings.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
A booking from first to last adds 1 at diff[first] and subtracts 1 at diff[last + 1], which is why the array
has one slot more than there are seats. The running total of the marks rises by 1 where a booking starts and falls by
1 just after it ends, so at every seat it equals the number of bookings covering it, exactly as the seat-by-seat
check shows. Recording a booking costs two additions however long it is; reading the result costs one pass.
Prefix sums or difference arrays?
The two are inverses: the running total of a difference array gives the values back, and the differences of neighbouring prefix entries give the original items back. Which one to keep depends on whether the data is mostly asked about or mostly changed.
| Criterion | Prefix sums | Difference array |
|---|---|---|
| Answers | the total of any range | the value of every item, once the updates are in |
| Setup | one pass, O(n) | an array of n + 1 zeros |
| Each update | O(n): every later prefix shifts | O(1): two marks per range |
| Each query | O(1): one subtraction | O(n) to rebuild the values first |
| When to choose | Many range questions about data that does not change | Many range updates applied before anything is read |
Three extensions build on the same idea. On a grid, the total of any rectangle comes from four corner values of a two-dimensional prefix table. Totals need a type wide enough for the largest one: 100,000 values of up to 10⁹ add up to 10¹⁴, beyond any 32-bit integer. When updates and queries arrive mixed together, a Fenwick tree gives both in O(log n).
Exercise
A payments dashboard asks the total of many date ranges over daily figures. The starter code is correct and far too slow; one prefix array fixes it.
Exercise · Easy · Python, JavaScript
Totals of daily payments over many date ranges
A payments dashboard keeps the total received on each day, payments[0] to payments[n - 1] (synthetic figures in whole currency units), and must answer many questions of the form "how much came in from day first to day last, both included?". Write range_totals(payments, queries) (JavaScript: rangeTotals(payments, queries)), where queries is a list of (first, last) pairs (JavaScript: [first, last] arrays) with 0 <= first <= last < n, and return the list of their totals, in the same order.
The starter code adds up every range one by one: it is correct, and far too slow for the last sample test, which asks 200,000 questions about 200,000 days. Daily totals reach 1,000,000,000, so in JavaScript make sure your totals stay exact.
Python · Starter code · range_totals.py
def range_totals(payments, queries):
"""The total of payments[first..last], both included, for each (first, last) in queries."""
# This adds up every range again: correct, but O(n) per question.
return [sum(payments[first : last + 1]) for first, last in queries] The sample tests · test_range_totals.py
import random
from range_totals import range_totals
def test_small():
"""totals of a few ranges of a week, single days included"""
week = [120, 0, 75, 300, 0, 45, 210]
assert range_totals(week, [(0, 6), (1, 3), (4, 4), (6, 6), (0, 0)]) == [750, 375, 0, 210, 120]
def test_no_queries():
"""no questions give an empty list"""
assert range_totals([5, 6], []) == []
def test_large_values():
"""totals beyond 2**31 come out exact"""
payments = [1_000_000_000] * 5
assert range_totals(payments, [(0, 4), (2, 3)]) == [5_000_000_000, 2_000_000_000]
def test_many_questions():
"""200,000 questions about 200,000 days finish in time and match a running total"""
rng = random.Random(2)
n = 200_000
payments = [rng.randrange(0, 1_000_000_001) for _ in range(n)]
queries = []
for _ in range(200_000):
first = rng.randrange(n // 4)
queries.append((first, rng.randrange(n - n // 4, n)))
totals = range_totals(payments, queries)
running = [0]
for p in payments:
running.append(running[-1] + p)
assert totals == [running[last + 1] - running[first] for first, last in queries] JavaScript · Starter code · range_totals.mjs
/** The total of payments[first..last], both included, for each [first, last] in queries. */
export function rangeTotals(payments, queries) {
// This adds up every range again: correct, but O(n) per question.
return queries.map(([first, last]) => {
let total = 0;
for (let day = first; day <= last; day++) total += payments[day];
return total;
});
} The sample tests · range_totals.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { rangeTotals } from './range_totals.mjs';
test('totals of a few ranges of a week, single days included', () => {
const week = [120, 0, 75, 300, 0, 45, 210];
assert.deepEqual(rangeTotals(week, [[0, 6], [1, 3], [4, 4], [6, 6], [0, 0]]), [750, 375, 0, 210, 120]);
});
test('no questions give an empty list', () => assert.deepEqual(rangeTotals([5, 6], []), []));
test('totals beyond 2**31 come out exact', () => {
assert.deepEqual(rangeTotals([1e9, 1e9, 1e9, 1e9, 1e9], [[0, 4], [2, 3]]), [5e9, 2e9]);
});
test('200,000 questions about 200,000 days finish in time and match a running total', () => {
let x = 2;
const next = (bound) => (x = (x * 48271) % 2147483647) % bound;
const n = 200000;
const payments = Array.from({ length: n }, () => next(1000000001));
const queries = Array.from({ length: 200000 }, () => [next(n / 4), n - n / 4 + next(n / 4)]);
const totals = rangeTotals(payments, queries);
const running = [0];
for (const p of payments) running.push(running[running.length - 1] + p);
assert.deepEqual(totals, queries.map(([first, last]) => running[last + 1] - running[first]));
}); A hint
Build prefix with n + 1 entries first: prefix[0] = 0 and prefix[i + 1] = prefix[i] + payments[i]. Then each query is one subtraction, prefix[last + 1] - prefix[first], so the whole job is O(n + q).
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Grids: two-dimensional prefix sums and difference grids, worked Pro: opens with a Pro, Premium or Ultimate pass
On a grid, a prefix table answers the total of any rectangle with four lookups, and a difference grid applies a rectangle update with four marks. This part works both on a 4 × 5 grid, checks them against every rectangle, and reproduces the off-by-one that the border of zeros prevents.
In this part:
- Inclusion and exclusion on a 4 × 5 grid, worked
- The off-by-one that the border prevents, reproduced
- A difference grid: four marks per rectangle
1 runnable example (Python) · about 2 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
64-bit totals, five languages and measured speed Pro: opens with a Pro, Premium or Ultimate pass
Prefix totals grow with the data, and a type that holds every item can still overflow on the totals. This part sizes the totals, reproduces the overflow in Java, C++ and Go and the loss of precision in JavaScript, and measures what a difference array saves on 100,000 bookings.
In this part:
- How large the totals get, with numbers
- Prefix sums in four more languages, with the overflow reproduced
- A difference array against one seat at a time, measured
3 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Beyond sums and fixed data: counts, XOR and changing arrays Pro: opens with a Pro, Premium or Ultimate pass
A prefix table works for any operation that can be undone, and it stops working when the data changes between questions. This part shows which operations qualify, why maximum does not, and how to choose between patching, rebuilding and a tree, with break-even numbers.
In this part:
- Counts and XOR work; maximum does not
- Negative numbers: why windows fail and running totals do not
- When updates and queries mix, with break-even numbers
1 runnable example (Python) · about 2 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Interview questions
-
Fresher · Warm-up
How does a difference array apply many range increments at once?
It records each update by its two ends instead of touching every item. To add v to items first to last, add v at diff[first] and subtract v at diff[last + 1], which is why the array has one slot more than the data. After all the updates are recorded, a single running total over diff gives every item's final value: the total rises by v where a range starts and falls back just after it ends, so each item receives exactly the updates that cover it. Recording costs O(1) per update and the final pass O(n), so q updates cost O(n + q) instead of up to n × q. It fits updates that all arrive before the values are read.
-
Fresher to mid
A booking system holds 200,000 bookings, each reserving a range of seats on a coach of 100,000 seats, and must report how many bookings cover each seat. A loop that adds 1 to every seat of every booking takes too long. Explain the cost with numbers and fix it.
-
Mid to senior
A dashboard must return the total of any rectangle of a 2,000 × 2,000 grid of daily sales figures, each up to 1,000,000, about a million times a day, using at most 64 MB. Choose the structure, its element type and its memory with numbers.
-
Mid to senior
Range increments now arrive interleaved with point queries, about 100,000 of each, on 1,000,000 positions: a query asks for one position's current value. Why does a difference array stop working, and what would you use instead? Compare the costs.
Key takeaways
- Prefix sums turn range totals into one subtraction:
prefix[last + 1] − prefix[first], after O(n) work once. - Keep
prefix[0] = 0and think in half-open ranges; the off-by-one slips disappear. - Difference arrays record a range update as two marks; one running total applies them all, O(n + q).
- Prefix sums suit fixed data with many questions; difference arrays suit many updates read once at the end.
References
- itertools.accumulate() (Python Software Foundation)
- Number.MAX_SAFE_INTEGER (MDN Web Docs (Mozilla))
- The Java Language Specification, Java SE 25: 4.2.2 Integer Operations (Oracle)
- Class Math (Java SE 25 API): addExact (Oracle)
- The Go Programming Language Specification: integer overflow (The Go Authors)
- std::partial_sum (cppreference.com)
- numpy.cumsum (NumPy Developers)
- UndefinedBehaviorSanitizer (The LLVM Project)
- Computer Science Curricula 2023: Algorithmic Foundations (AL) (ACM, IEEE Computer Society and AAAI)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress