Aggregate as a General-Purpose Fold
Aggregate() is LINQ's implementation of the classic functional-programming 'fold' or 'reduce' operation: it walks the sequence one element at a time, threading an accumulator value through a function you supply, func(accumulator, element) => newAccumulator, and returns the final accumulator once the sequence is exhausted. The simplest overload, Aggregate(func), uses the first element as the seed and starts folding from the second element, which means it throws InvalidOperationException on an empty sequence since there's no first element to seed with.
Cricket analogy: A scorer building up the running total after every single ball — starting from the first run scored and adding each subsequent delivery's runs — mirrors Aggregate() threading an accumulator through each element one at a time.
Seeded Aggregate and Result Selectors
The two-argument overload, Aggregate(seed, func), lets you supply an explicit starting value of any type — not necessarily the element type — which both avoids the empty-sequence exception (an empty source just returns the seed unchanged) and lets the accumulator be a different shape than the elements, such as folding a sequence of strings into a StringBuilder or a running Dictionary<string,int> tally. The three-argument overload, Aggregate(seed, func, resultSelector), adds a final transformation applied only once to the finished accumulator, useful when you want to fold into an internal working type (like a mutable struct or tuple) but return a cleaner public-facing shape.
Cricket analogy: Starting a team's tournament points tally at zero before a ball is bowled, so an abandoned tournament with no matches still reports '0 points' instead of erroring, mirrors how a seeded Aggregate() gracefully handles empty sequences.
When to Reach for Aggregate
Aggregate() should be your last resort, not your first instinct — for common cases like summing, counting, finding min/max, or string concatenation, the dedicated operators (Sum(), Count(), Min(), Max(), string.Join()) are more readable, often more optimized, and clearer to a future reader than an equivalent Aggregate() lambda. Reach for Aggregate() when you need genuinely custom accumulation logic that no built-in operator expresses, such as computing a running maximum-drawdown in a stock price series, building a state machine that transitions based on each element, or folding a token stream into a parsed structure.
Cricket analogy: A commentator wouldn't manually recompute a batter's strike rate ball-by-ball with custom arithmetic when the scoreboard already tracks it — but they would build custom logic for something novel like 'longest boundary drought', just as Aggregate() is reserved for logic no built-in LINQ operator covers.
var prices = new List<decimal> { 100m, 105m, 98m, 120m, 90m, 130m };
// Simple overload: seeds from the first element, throws on empty
var product = prices.Aggregate((acc, p) => acc * p);
// Seeded overload: safe on empty sequences, accumulator can differ in type
var wordCounts = new[] { "the", "quick", "the", "fox", "quick", "the" }
.Aggregate(new Dictionary<string, int>(), (dict, word) =>
{
dict[word] = dict.GetValueOrDefault(word) + 1;
return dict;
});
// Three-argument overload: custom internal state, clean public result
var maxDrawdown = prices.Aggregate(
seed: (peak: prices[0], maxDrawdown: 0m),
func: (state, price) =>
{
var peak = Math.Max(state.peak, price);
var drawdown = (peak - price) / peak;
return (peak, Math.Max(state.maxDrawdown, drawdown));
},
resultSelector: state => state.maxDrawdown);
Console.WriteLine($"Max drawdown: {maxDrawdown:P1}");The unseeded Aggregate(func) overload throws InvalidOperationException on an empty sequence, exactly like First(), because it uses the first element as the implicit seed. If the source could plausibly be empty, always use the seeded overload with a sensible default instead.
Aggregate() is not lazy — like Sum() and Count(), it forces immediate execution and walks the entire sequence synchronously in order, so it cannot be used for infinite sequences (unlike Take() or TakeWhile(), which can short-circuit lazily).
- Aggregate() implements a general fold/reduce, threading an accumulator through each element via a supplied function.
- The unseeded overload uses the first element as the seed and throws on an empty sequence.
- The seeded overload, Aggregate(seed, func), avoids the empty-sequence exception and allows a different accumulator type.
- The three-argument overload adds a resultSelector applied once to the final accumulator, decoupling internal state from the public result.
- Prefer built-in operators (Sum, Count, Min, Max, string.Join) over Aggregate() for standard cases — they're clearer and often faster.
- Reach for Aggregate() only for genuinely custom, stateful accumulation logic with no dedicated operator.
- Aggregate() forces immediate, synchronous execution across the entire sequence, so it can't handle infinite sequences.
Practice what you learned
1. What does the unseeded Aggregate(func) overload use as its initial accumulator value?
2. Why does the seeded Aggregate(seed, func) overload avoid throwing on an empty sequence?
3. What is the purpose of the resultSelector parameter in the three-argument Aggregate overload?
4. Which of these is the best candidate for using Aggregate() rather than a built-in operator?
5. Is Aggregate() a deferred or immediate execution operator?
Was this page helpful?
You May Also Like
Aggregate Functions: Sum, Count, Average
Learn how LINQ's Sum, Count, and Average operators collapse sequences into single scalar values, and how execution timing and edge cases affect their use.
Any, All, and Contains
Master LINQ's boolean-returning quantifier operators — Any, All, and Contains — and how their short-circuiting behavior makes them efficient existence checks.
First, Single, and ElementAt
Understand the differences between First, Single, and ElementAt for extracting one element from a LINQ sequence, and how their exception behavior differs.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics