C++ Module 11 – Iterators, algorithms and ranges
C++ <numeric>: accumulate, reduce, prefix sums
How std::accumulate, std::reduce and prefix sums with std::partial_sum work, the type traps in each, and std::gcd, std::lcm, std::midpoint and std::iota.
What you will learn
- Combine values with std::accumulate and std::reduce, and choose the type of the total
- Build prefix sums with partial_sum or inclusive_scan to answer range-sum queries
- Explain why std::reduce can give a different floating-point total from std::accumulate
- Use std::gcd, std::lcm, std::midpoint and std::iota without overflow
Before you start
What you’ll learn · Before you start
What you will learn
- Combine values with std::accumulate and std::reduce, and choose the type of the total
- Build prefix sums with partial_sum or inclusive_scan to answer range-sum queries
- Explain why std::reduce can give a different floating-point total from std::accumulate
- Use std::gcd, std::lcm, std::midpoint and std::iota without overflow
Before you start
On this page
Four deposits of 1,000,000,000 paise add up to 4,000,000,000, yet std::accumulate(deposits.begin(), deposits.end(), 0)
returns -294,967,296 with both compilers, and nothing warns: the initial 0 made the running total an int. Traps
like this are why <numeric> is worth knowing.
std::accumulate fold: Combining the elements of a range into one value with an operation, starting from an initial value: a left fold of 1, 2 and 3 with + computes ((0 + 1) + 2) + 3. std::accumulate and C++23's std::ranges::fold_left fold from the left. a range from left to right, starting from an initial value.
std::reduce (C++17) computes the same kind of total but may group and reorder the additions, so it can run in
parallel, and std::transform_reduce first applies a function to each element, or to each pair of two ranges.
std::partial_sum and std::inclusive_scan write running totals, the prefix sums: An array whose position i holds the total of the first i values, with 0 at position 0, so the total of the half-open range [l, r) is prefix[r] - prefix[l], one subtraction whatever its length. std::partial_sum and std::inclusive_scan write the running totals. that
answer any “total from position l to r” question with one subtraction, and std::adjacent_difference turns running
totals back into values. std::iota fills a range with increasing values, and std::gcd, std::lcm and
std::midpoint do integer arithmetic that is easy to get wrong by hand.
accumulate adds in the type of its initial value
Four deposits and three hours, added from different starting values:
// std::accumulate adds in the type of its initial value: 0 means int, whatever the elements are.
#include <algorithm>
#include <functional>
#include <numeric>
#include <print>
#include <vector>
int main() {
std::vector<long long> deposits(4, 1'000'000'000); // four deposits of 1,000,000,000 paise each
std::println("accumulate with 0: {}", std::accumulate(deposits.begin(), deposits.end(), 0));
std::println("accumulate with 0LL: {}", std::accumulate(deposits.begin(), deposits.end(), 0LL));
std::vector<double> hours{0.5, 0.75, 1.25};
std::println("accumulate with 0: {}", std::accumulate(hours.begin(), hours.end(), 0));
std::println("accumulate with 0.0: {}", std::accumulate(hours.begin(), hours.end(), 0.0));
// C++23's fold_left keeps the total in the type that the operation returns: int + long long is long long.
std::println("fold_left with 0: {} and {}", std::ranges::fold_left(deposits, 0, std::plus<>{}),
std::ranges::fold_left(hours, 0, std::plus<>{}));
} from session import run
run("""
clang++ -std=c++23 -Wall -Wextra accumulate_type.cpp -o accumulate_type && ./accumulate_type
g++-16 -std=c++23 -Wall -Wextra accumulate_type.cpp -o accumulate_type-gcc && ./accumulate_type-gcc
""") Output
$ clang++ -std=c++23 -Wall -Wextra accumulate_type.cpp -o accumulate_type && ./accumulate_type accumulate with 0: -294967296 accumulate with 0LL: 4000000000 accumulate with 0: 1 accumulate with 0.0: 2.5 fold_left with 0: 4000000000 and 2.5 $ g++-16 -std=c++23 -Wall -Wextra accumulate_type.cpp -o accumulate_type-gcc && ./accumulate_type-gcc accumulate with 0: -294967296 accumulate with 0LL: 4000000000 accumulate with 0: 1 accumulate with 0.0: 2.5 fold_left with 0: 4000000000 and 2.5
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 accumulate_type_session.py
With 0, the running total is an int. For the deposits, each addition happens in long long and the result is
stored back into the int. Since C++20 that conversion is defined as wrapping round modulo 2³², so both compilers
print -294,967,296 every time: a wrong answer that looks plausible at a glance. For the hours, every step cuts the
double back to a whole number, so 0.5 and 0.75 vanish and only the 1 of 1.25 survives. Step through the hours,
added once from 0 and once from 0.0:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Two starting values. std::accumulate keeps its running total in the type of the initial value. Pass 0 and the total is an int; pass 0.0 and it is a double. The hours themselves are doubles either way.
- The first hour. Each step computes total + element in double, because one operand is a double, and stores the result back into the total. 0.5 fits a double, but stored in an int it becomes 0.
- The second hour. On the left the total is still 0, so 0 + 0.75 is 0.75 and is cut back to 0 again. On the right 0.5 + 0.75 is 1.25 and is kept.
- The third hour. 0 + 1.25 is 1.25, and the int keeps only the 1. On the right 1.25 + 1.25 is 2.5.
- Two different answers. Both calls return their total in the initial value's type: 1 from 0, and 2.5 from 0.0, exactly what both compilers printed. Nothing warned, because every conversion happened inside the library.
Passing 0LL or 0.0 fixes both. C++23’s std::ranges::fold_left avoids the trap by design: its total takes the
type that the operation returns, and adding a long long to an int returns a long long. When the elements and the
initial value are both int, an overflowing sum is not even wrapped: it is undefined behaviour.
The same rule explains a common compile error: std::accumulate(words.begin(), words.end(), "") on a vector of
strings does not compile, because the initial value makes the total a const char*, and a joined
std::string cannot be stored in one. With std::string{} as the initial value it compiles and joins the words.
Write the initial value in the type you want the total to have.
Checkpoint
Check: The type of an accumulate total
reduce may regroup the additions, so the operation must allow it
std::reduce returns what accumulate would, as long as the operation does not care how the values are grouped or
ordered. The standard lets it apply the operation in an unspecified order, so that it can split the range across
threads when given an
An argument such as std::execution::par (C++17, header <execution>) that permits a standard algorithm to spread its work over several threads, or to use vector instructions.,
and it notes that the result is then nondeterministic for operations such as floating-point addition. For integers
that changes nothing; for float and double the last digits can change, and with large and small values mixed,
much more than the last digits. Step through eight whole numbers added in two orders:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- One total, two orders. The rain of eight days, 3, 1, 4, 1, 5, 9, 2 and 6, is to be totalled from 0. accumulate must add them one at a time from the left; reduce may group and reorder them, so that parts can be added at the same time.
- Left to right. accumulate computes ((((0 + 3) + 1) + 4) + 1): every addition needs the result of the one before, so no two of them can run at the same time. After four values its total is 9.
- Pairs first. One grouping reduce may use adds neighbours in pairs: 3 + 1, 4 + 1, 5 + 9 and 2 + 6. The four additions do not depend on each other, so four threads could do one each.
- Then pairs of pairs. The pair sums 4, 5, 14 and 8 are added in pairs again, 9 and 22, while accumulate is still working through 5, 9, 2 and 6 one at a time.
- The same 31. Both reach 31, the total range_queries.cpp printed for all 8 days. Integer addition gives the same answer in any grouping, so reduce is safe here; floating-point addition rounds at every step, so a different grouping can change the result.
Use reduce when the operation is associative and commutative, such as integer addition, a maximum or a bitwise or,
and keep accumulate when the order is part of the result: subtraction, joining strings, or floating-point totals
that must come out the same everywhere. Three questions decide between the three folds:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Algorithm | Order of the operations | Type of the total | Same total every time | Execution policies | Standard |
|---|---|---|---|---|---|
| std::accumulate | Good left to right, one element at a time | Mixed the initial value's type: 0 makes it an int | Good yes | Poor none | Good C++98 |
| std::reduce | Mixed any grouping and any order | Mixed the initial value's type, as for accumulate | Mixed only for associative, commutative operations | Good yes, such as std::execution::par | Good C++17 |
| std::ranges::fold_left | Good left to right, one element at a time | Good what the operation returns | Good yes | Poor none | Mixed C++23, in recent compilers |
How the answers decide
- “Does the order of the operations matter, as for subtraction, joined strings or a float total that must match everywhere?” Yes, the order is part of the result: rules out std::reduce. reduce may regroup and reorder the operations, so subtraction, joined strings and float totals can come out differently.
- “Will the total be computed on several threads, with an execution policy?” Yes, in parallel: rules out std::accumulate and std::ranges::fold_left. Only reduce takes an execution policy; accumulate and fold_left always apply the operation one step after another.
- “Could the total outgrow the initial value's type, as an int 0 does with long long values?” Yes, it could: adds a caveat to std::accumulate and std::reduce. Write the initial value in the type the total needs, such as 0LL or 0.0; fold_left takes the type that the operation returns.
Checkpoint
Check: Why reduce may give another total
Prefix sums answer range totals with one subtraction
Eight days of rain and their prefix sums; the program builds them and answers two questions:
Prefix sums turn any range total into one subtraction
Text description of the diagram
The diagram has three parts, one below the other.
- The rain on days 0 to 7: 3, 1, 4, 1, 5, 9, 2 and 6 millimetres.
- The prefix sums, one more value than there are days: 0, 3, 4, 8, 9, 14, 23, 25 and 31. Position i holds the total of the first i days, so position 0 is 0 and position 8 is the total of all eight days. std::partial_sum writes them from position 1.
- A question answered with one subtraction: the rain on days 2 to 5 is the prefix at position 6 minus the prefix at position 2, 23 minus 4, which is 19.
// Prefix sums answer "what is the total from day l to day r?" with one subtraction.
#include <numeric>
#include <print>
#include <vector>
int main() {
std::vector<long long> rain{3, 1, 4, 1, 5, 9, 2, 6}; // millimetres of rain on days 0 to 7
// prefix[i] is the total of the first i days, so prefix[0] = 0 and prefix has one element more than rain.
std::vector<long long> prefix(rain.size() + 1, 0);
std::partial_sum(rain.begin(), rain.end(), prefix.begin() + 1);
std::println("prefix: {}", prefix);
// The total of days [l, r) is prefix[r] - prefix[l]: no loop, whatever the length.
auto total = [&](std::size_t l, std::size_t r) { return prefix[r] - prefix[l]; };
std::println("days 2 to 5: {}", total(2, 6));
std::println("all 8 days: {}", total(0, 8));
// adjacent_difference undoes partial_sum: it gives back the daily values.
std::vector<long long> back(prefix.size());
std::adjacent_difference(prefix.begin(), prefix.end(), back.begin());
std::println("adjacent_difference of prefix: {}", back);
// C++17's scans: inclusive_scan matches partial_sum; exclusive_scan starts from an initial value instead.
std::vector<long long> inc(rain.size()), exc(rain.size());
std::inclusive_scan(rain.begin(), rain.end(), inc.begin());
std::exclusive_scan(rain.begin(), rain.end(), exc.begin(), 0LL);
std::println("inclusive_scan: {}", inc);
std::println("exclusive_scan: {}", exc);
} from session import run
run("""
clang++ -std=c++23 -Wall -Wextra range_queries.cpp -o range_queries && ./range_queries
g++-16 -std=c++23 -Wall -Wextra range_queries.cpp -o range_queries-gcc && ./range_queries-gcc
""") Output
$ clang++ -std=c++23 -Wall -Wextra range_queries.cpp -o range_queries && ./range_queries prefix: [0, 3, 4, 8, 9, 14, 23, 25, 31] days 2 to 5: 19 all 8 days: 31 adjacent_difference of prefix: [0, 3, 1, 4, 1, 5, 9, 2, 6] inclusive_scan: [3, 4, 8, 9, 14, 23, 25, 31] exclusive_scan: [0, 3, 4, 8, 9, 14, 23, 25] $ g++-16 -std=c++23 -Wall -Wextra range_queries.cpp -o range_queries-gcc && ./range_queries-gcc prefix: [0, 3, 4, 8, 9, 14, 23, 25, 31] days 2 to 5: 19 all 8 days: 31 adjacent_difference of prefix: [0, 3, 1, 4, 1, 5, 9, 2, 6] inclusive_scan: [3, 4, 8, 9, 14, 23, 25, 31] exclusive_scan: [0, 3, 4, 8, 9, 14, 23, 25]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 range_queries_session.py
prefix[i] holds the total of the first i values and prefix[0] is 0, so the total of the half-open range
[l, r) is prefix[r] - prefix[l]: one subtraction, however long the range. Building the array takes one pass with
std::partial_sum, written from position 1. std::adjacent_difference reverses it, which is why the round trip prints
the daily values again, behind the 0. C++17’s std::inclusive_scan writes the same running totals as partial_sum,
and std::exclusive_scan writes the total before each position, starting from an initial value. Step through the
build and two questions:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- One slot more than days. prefix has 9 elements for 8 days, all 0 to start with. prefix[i] will hold the total of the first i days, so prefix[0] stays 0, and partial_sum writes from position 1.
- The first day. partial_sum reads day 0, 3 millimetres. The running total is 3, and it writes 3 into prefix[1].
- A running total. Each later day is added to the total so far: 3 + 1 = 4 goes to prefix[2] and 4 + 4 = 8 to prefix[3].
- To the last day. Five more additions finish the array: 9, 14, 23, 25 and 31. One pass over the data, eight additions, and the program printed prefix: [0, 3, 4, 8, 9, 14, 23, 25, 31].
- Days 2 to 5. Days 2 to 5 are the half-open range [2, 6). Their total is prefix[6] - prefix[2], the first six days minus the first two: 23 - 4 = 19, as the program printed.
- Any length, the same cost. All 8 days are [0, 8): prefix[8] - prefix[0] = 31 - 0 = 31. A range of eight days costs one subtraction, the same as a range of one day.
- And back again. adjacent_difference subtracts each element from the one after it, so it turns the running totals back into 0 followed by the daily values: [0, 3, 1, 4, 1, 5, 9, 2, 6].
The cost is one pass to build the array and one subtraction per question, against a walk over the whole range for every question when you add it up again. That trade is why prefix sums are the standard tool for many questions about data that does not change. When values change between questions, the array would have to be rebuilt; a Fenwick tree handles both an update and a range total in O(log n) steps.
Checkpoint
Check: Range totals from prefix sums
gcd, lcm, midpoint and iota do small integer jobs
Four more functions of <numeric> handle integer arithmetic that hand-written code often gets wrong:
std::gcd(m, n)(C++17) returns the greatest common divisor of the absolute values, and 0 when both are 0.std::lcm(m, n)(C++17) returns the least common multiple, which must fit in the result type.std::midpoint(a, b)(C++20) returns half the sum without overflowing, rounded towardsafor integers.std::iota(first, last, value)fills a range withvalue,value + 1and so on; C++23 addsstd::ranges::iota.
// std::iota fills a range with increasing values; std::gcd, std::lcm and std::midpoint do small integer jobs.
#include <numeric>
#include <print>
#include <vector>
int main() {
std::vector<int> seats(6);
std::iota(seats.begin(), seats.end(), 101); // 101, 102, ..., 106
std::println("seats: {}", seats);
// The largest square tile that covers a 48 x 18 floor exactly, with no tile cut.
std::println("gcd(48, 18) = {}", std::gcd(48, 18));
// Two lights that blink every 4 and every 6 seconds blink together every lcm seconds.
std::println("lcm(4, 6) = {}", std::lcm(4, 6));
// Half the sum, computed in a way that cannot overflow.
std::println("midpoint(10, 20) = {}", std::midpoint(10, 20));
} Output
seats: [101, 102, 103, 104, 105, 106] gcd(48, 18) = 6 lcm(4, 6) = 12 midpoint(10, 20) = 15
Recorded with Apple clang version 21.0.0 (clang-2100.3.34.2) on macOS 26 arm64. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined helpers.cpp -o helpers && ./helpers
Each job is one call: seats from 101, a tile of side 6 for a 48 by 18 floor, lights that blink together every 12 seconds and 15 halfway between 10 and 20. Small positive test values like these never reach the edges: negative numbers, zeros, results too large for their type and odd sums.
Checkpoint
Check: iota, gcd, lcm and midpoint
Practise: totals, types and ranges
The questions below start where your answers have been weakest, and each choice names the slip it guards against.
Practice that adapts to you
Practise this lesson
8 questions about 8 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Prefix sums at scale: grids, overflow budgets and timed queries Pro: opens with a Pro, Premium or Ultimate pass
Prefix sums are the standard answer to range questions in reports and in coding rounds, and the details decide whether the answer is right: the type of the running total, the extra zero row of a grid and the cost of the alternative. This part works through all three with code that runs, then gives you an exercise in one and two dimensions.
In this part:
- A running total kept in the input’s type, reproduced
- Choosing the type of a prefix array: an overflow budget, worked
- Rectangle sums in a grid, worked by hand
- Measured: adding each range against one subtraction per query
- Your turn: range sums in one dimension and two
1 diagram · 8 runnable examples (C header, C++, Python) · about 8 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).
reduce, transform_reduce, gcd, lcm and midpoint: the traps, reproduced Pro: opens with a Pro, Premium or Ultimate pass
Each of these functions has one way to go wrong that a quick test does not show. This part reproduces each failure on both standard libraries and shows the version that holds.
In this part:
- The same floats, two libraries, two different totals from reduce
- Weighted totals with transform_reduce
- gcd and lcm: signs, zeros and the overflow nobody checks
- Midpoints without overflow, and which way they round
- Choosing between them: a summary with the trade-offs
8 runnable examples (C++, Python) · about 6 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Interview questions
-
Fresher · Warm-up
What does std::accumulate(v.begin(), v.end(), 0) return for std::vector<double> v{0.5, 0.75, 1.25}, and why?
It returns 1, as an int. accumulate keeps its running total in the type of the initial value, and 0 is an int, so every step converts the sum back to a whole number: 0 + 0.5 becomes 0, 0 + 0.75 becomes 0, and 0 + 1.25 becomes 1. Nothing warns about it, because the conversion happens inside the library. Passing 0.0 gives 2.5. The same rule makes a sum of large long long values with an initial 0 wrap round, since the total is still an int, and makes an initial "" fail to compile when joining strings. A good answer adds that C++23's std::ranges::fold_left takes the type of its total from what the operation returns, so it gives 2.5 even with 0.
-
Fresher to mid
You receive 200,000 daily sales figures, each up to 10^9 paise, and then 200,000 questions of the form "total sales from day l to day r". Design the solution, choose the types and give its cost. What changes if figures are corrected between questions?
-
Mid to senior
A reporting job switched from std::accumulate to std::reduce, and now the float total of a revenue column changes in its last digits and differs between the Linux build with GCC and the Mac build with Clang. Is this a bug? Explain, and choose a fix.
-
Fresher to mid
A binary search over byte offsets in a 3 GB file uses int lo and hi and computes mid = (lo + hi) / 2. It works on small files and hangs or crashes on large ones. Explain both failures, fix them, and say what std::midpoint guarantees.
Key takeaways
std::accumulateadds in the type of its initial value: write0LLor0.0, never a bare0, for large or fractional sums.std::reducemay regroup the additions, so floating-point totals can differ fromaccumulate.- Prefix sums turn the total of [l, r) into
prefix[r] - prefix[l]. gcd,lcmandmidpointhandle signs and overflow for you, except that thelcmitself must fit in its result type.
Terms in this lesson
- fold
- Combining the elements of a range into one value with an operation, starting from an initial value: a left fold of 1, 2 and 3 with + computes ((0 + 1) + 2) + 3. std::accumulate and C++23's std::ranges::fold_left fold from the left.
- prefix sums
- An array whose position i holds the total of the first i values, with 0 at position 0, so the total of the half-open range [l, r) is prefix[r] - prefix[l], one subtraction whatever its length. std::partial_sum and std::inclusive_scan write the running totals.
References
- Working Draft, Programming Languages: C++ (N4950) (ISO/IEC JTC1/SC22/WG21)
- P0024R2: The Parallelism TS Should be Standardized (ISO/IEC JTC1/SC22/WG21)
- P0295R0: Adopt Selected Library Fundamentals V2 Components for C++17 (ISO/IEC JTC1/SC22/WG21)
- P0811R3: Well-behaved interpolation for numbers and pointers (ISO/IEC JTC1/SC22/WG21)
- P2322R6: ranges::fold (ISO/IEC JTC1/SC22/WG21)
- P2440R1: ranges::iota, ranges::shift_left, and ranges::shift_right (ISO/IEC JTC1/SC22/WG21)
- P1236R1: Alternative Wording for P0907R4 Signed Integers are Two's Complement (ISO/IEC JTC1/SC22/WG21)
- UndefinedBehaviorSanitizer (Clang documentation) (LLVM Project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress