C#
LINQ
programming
coding
one-liner

Accumulating sum in one line in C

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In C#, an accumulating (running) sum transforms a sequence like [1, 2, 3, 4] into [1, 3, 6, 10], where each element is the sum of all preceding elements including itself. You can compute this in one line using LINQ's Aggregate() method, the Scan pattern, or — starting with .NET 6 — a simple foreach with Sum(). For a true running sum that produces a new sequence (not just a final total), combine Select with a captured variable or use Aggregate with a list accumulator.

Running Sum with Select and Closure

csharp
1int[] numbers = { 1, 2, 3, 4, 5 };
2
3// One-liner: running sum using Select with a captured variable
4int sum = 0;
5int[] runningSum = numbers.Select(x => sum += x).ToArray();
6// Result: [1, 3, 6, 10, 15]

The lambda captures sum by reference and mutates it on each iteration. Each call to the lambda adds the current element to sum and returns the new total. This is concise but relies on side effects.

Using Aggregate for a Running Sum

csharp
1int[] numbers = { 1, 2, 3, 4, 5 };
2
3// Aggregate builds a list of cumulative sums
4var runningSum = numbers.Aggregate(
5    new List<int>(),
6    (acc, x) => { acc.Add(acc.Count == 0 ? x : acc[^1] + x); return acc; }
7);
8// Result: [1, 3, 6, 10, 15]

Aggregate applies a function to each element, passing the accumulated result forward. Here the accumulator is a List<int> where each new element is the previous sum plus the current value.

Simple Total Sum (One-Liner)

csharp
1int[] numbers = { 1, 2, 3, 4, 5 };
2
3// Total sum — not a running sum
4int total = numbers.Sum();           // 15
5int total2 = numbers.Aggregate(0, (acc, x) => acc + x);  // 15
6
7// With a selector
8var orders = new[] {
9    new { Name = "A", Amount = 10.5m },
10    new { Name = "B", Amount = 20.3m },
11    new { Name = "C", Amount = 5.2m }
12};
13decimal totalAmount = orders.Sum(o => o.Amount);  // 36.0

Running Sum with Zip (No Side Effects)

csharp
1int[] numbers = { 1, 2, 3, 4, 5 };
2
3// Pure functional approach using scan pattern
4int[] runningSum = numbers
5    .Select((x, i) => numbers.Take(i + 1).Sum())
6    .ToArray();
7// Result: [1, 3, 6, 10, 15]

This approach is pure (no side effects) but has O(n^2) complexity since Take(i+1).Sum() re-sums from the start for each element. It is suitable for small collections but inefficient for large ones.

Scan Extension Method

csharp
1// Reusable Scan method (like Haskell's scanl)
2public static IEnumerable<TAccumulate> Scan<TSource, TAccumulate>(
3    this IEnumerable<TSource> source,
4    TAccumulate seed,
5    Func<TAccumulate, TSource, TAccumulate> func)
6{
7    var accumulator = seed;
8    foreach (var item in source)
9    {
10        accumulator = func(accumulator, item);
11        yield return accumulator;
12    }
13}
14
15// Usage
16int[] numbers = { 1, 2, 3, 4, 5 };
17int[] runningSum = numbers.Scan(0, (acc, x) => acc + x).ToArray();
18// Result: [1, 3, 6, 10, 15]
19
20// Running product
21int[] runningProduct = numbers.Scan(1, (acc, x) => acc * x).ToArray();
22// Result: [1, 2, 6, 24, 120]

A Scan extension method is the cleanest solution for running accumulations. It is O(n), lazy (uses yield return), and reusable for any accumulation operation.

Running Sum of Object Properties

csharp
1var transactions = new[]
2{
3    new { Date = "Jan", Amount = 100m },
4    new { Date = "Feb", Amount = -30m },
5    new { Date = "Mar", Amount = 50m },
6    new { Date = "Apr", Amount = -10m },
7};
8
9decimal balance = 0;
10var ledger = transactions.Select(t => new {
11    t.Date,
12    t.Amount,
13    Balance = balance += t.Amount
14}).ToArray();
15
16// Result:
17// Jan: +100, Balance: 100
18// Feb: -30,  Balance: 70
19// Mar: +50,  Balance: 120
20// Apr: -10,  Balance: 110

Common Pitfalls

  • Confusing Sum() with running sum: numbers.Sum() returns a single total value. For a running sum (cumulative sequence), you need Select with a closure, Aggregate with a list, or a custom Scan method.
  • Side effects in LINQ queries: Using Select(x => sum += x) mutates a captured variable, which breaks if the query is enumerated more than once. Each enumeration produces different results. Always call .ToArray() or .ToList() immediately to materialize the result.
  • O(n^2) running sum with Take(i+1).Sum(): The pure functional approach re-sums the entire prefix for each element, making it quadratic. For collections larger than a few hundred elements, use the closure or Scan approach instead.
  • Parallel LINQ (PLINQ) with side effects: Using .AsParallel().Select(x => sum += x) produces race conditions because multiple threads mutate sum concurrently. Never use mutable captured variables with PLINQ.
  • Forgetting Aggregate seed value: numbers.Aggregate((a, b) => a + b) throws InvalidOperationException on an empty sequence. Always provide a seed value: numbers.Aggregate(0, (a, b) => a + b).

Summary

  • numbers.Sum() returns the total — for a running sum, use Select with a captured variable or Aggregate with a list accumulator
  • The closure approach (sum += x inside Select) is the most concise one-liner but relies on side effects
  • A custom Scan extension method is the cleanest, most reusable solution for running accumulations
  • The pure Take(i+1).Sum() approach is O(n^2) — avoid it for large collections
  • Always materialize side-effect queries with .ToArray() to prevent multiple enumeration issues
  • Never use mutable captured variables with PLINQ — it causes race conditions

Course illustration
Course illustration

All Rights Reserved.