Question

LINQ Aggregate in C#: A Clear Guide with Examples

csharp.netlinqaggregate

Question

Can you provide a short, descriptive, and comprehensive explanation of LINQ's Aggregate method in C#, including a small, clear example that shows how it works?

Short Answer

By the end of this page, you will understand how LINQ's Aggregate method combines all items in a sequence into one result. You will learn what an accumulator is, when to supply a seed value, how each overload behaves, and when clearer alternatives such as Sum or string.Join are preferable.

Concept

Aggregate is a LINQ method that reduces a sequence to one final value. It processes items one at a time and carries forward a running result.

For example, a list of numbers can become one total, and a list of words can become one sentence. The running result is called the accumulator.

int[] numbers = { 2, 3, 4 };

int product = numbers.Aggregate((currentTotal, nextNumber) =>
    currentTotal * nextNumber);

Console.WriteLine(product); // 24

The lambda receives two values:

  • currentTotal: the result calculated so far
  • nextNumber: the next item from the sequence

It must return the next value for currentTotal.

Aggregate is useful when no specialized LINQ method describes the operation. For standard tasks, prefer expressive methods such as Sum, Count, Min, Max, or string.Join. They make intent easier to recognize.

Mental Model

Imagine a conveyor belt and a small calculator.

  • Items arrive on the belt one by one.
  • The calculator stores a running answer.
  • For every new item, it combines that item with the stored answer.
  • When the belt is empty, the stored answer is the final result.

For multiplication of 2, 3, and 4, the calculator starts with 2, then computes 2 × 3, then computes 6 × 4. Its final stored answer is 24.

A seed is like setting the calculator's starting value before the belt begins. For addition, the natural seed is usually 0; for multiplication, it is usually 1.

Take Quiz

Syntax and Examples

The most common Aggregate overloads are:

// Uses the first item as the initial accumulator.
TSource Aggregate<TSource>(Func<TSource, TSource, TSource> func)

// Starts with a supplied seed value.
TAccumulate Aggregate<TSource, TAccumulate>(
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func)

// Starts with a seed and converts the final accumulator to another result type.
TResult Aggregate<TSource, TAccumulate, TResult>(
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func,
    Func<TAccumulate, TResult> resultSelector)

Example: calculate a total with a seed

int[] prices = { 12, 8, 5 };

int total = prices.Aggregate(
    0,
    (runningTotal, price) => runningTotal + price);

Console.WriteLine(total); // 25

0 is the seed. Each price is added to runningTotal.

Example: build a comma-separated string

string[] tags = { "csharp", "linq", "arrays" };

string text = tags.Aggregate(
    "",
    (result, tag) => result == "" ? tag : result + ", " + tag);

Console.WriteLine(text); // csharp, linq, arrays

This works, but string.Join(", ", tags) is usually simpler for joining strings.

Example: use a result selector

int[] scores = { 10, 20, 30 };

string report = scores.Aggregate(
    0,
    (total, score) => total + score,
    total => $"Total score: {total}");

Console.WriteLine(report); // Total score: 60

The accumulator is an int, while the final result is a string.

Step by Step Execution

Consider this code:

int[] numbers = { 2, 3, 4 };

int product = numbers.Aggregate(
    1,
    (accumulator, number) => accumulator * number);

Execution trace:

Stepaccumulator beforenumberReturned value
Start1 (the seed)——
First item121 * 2 = 2
Second item232 * 3 = 6
Third item646 * 4 = 24

After the final item, Aggregate returns 24.

The accumulator function runs once per element when a seed is supplied. Without a seed, the first element becomes the initial accumulator, so the function runs once fewer than the number of elements.

Real World Use Cases

Aggregate can express custom reductions that do not have a dedicated LINQ method.

  • Shopping cart calculation: accumulate a subtotal from line items.
  • Permissions: combine permissions into a set of allowed actions.
  • Data summaries: build totals, counts, and derived statistics in one custom result object.
  • Request headers: combine header values into a dictionary when processing incoming data.
  • Log or report generation: turn records into a formatted report when a specialized formatting API does not fit.

For example, build a frequency dictionary:

string[] words = { "cat", "dog", "cat" };

Dictionary<string, int> counts = words.Aggregate(
    new Dictionary<string, int>(),
    (result, word) =>
    {
        result[word] = result.GetValueOrDefault(word) + 1;
        return result;
    });

Console.WriteLine(counts["cat"]); // 2

In production code, be aware that this example mutates the dictionary accumulator. That is acceptable in many local LINQ pipelines, but should be documented and used deliberately.

Take Quiz

Real Codebase Usage

In real C# projects, developers often choose the most specific operation first:

int total = prices.Sum();                 // Better than Aggregate for a sum
int highest = scores.Max();               // Better than Aggregate for a maximum
string labels = string.Join(", ", tags); // Better than Aggregate for joining text

Use Aggregate when the result is genuinely custom. A common pattern is to seed it with a dedicated accumulator object:

public sealed class OrderSummary
{
    public int ItemCount { get; set; }
    public decimal Total { get; set; }
}

OrderSummary summary = items.Aggregate(
    new OrderSummary(),
    (result, item) =>
    {
        result.ItemCount++;
        result.Total += item.Price * item.Quantity;
        return result;
    });

For input validation, developers commonly use All or Any instead of Aggregate because they communicate the intent clearly and can stop early:

bool allValid = users.All(user => user.Email.Contains('@'));

Aggregate always processes the sequence needed for its reduction; it is not usually the clearest tool for guard clauses or simple validation.

Common Mistakes

Omitting a seed for a possibly empty sequence

This overload throws InvalidOperationException when the source is empty:

int[] numbers = Array.Empty<int>();
int total = numbers.Aggregate((a, b) => a + b); // Throws

Provide an identity seed when an empty result has a sensible meaning:

int total = numbers.Aggregate(0, (a, b) => a + b); // 0

Choosing the wrong seed

A seed participates in the calculation, so it changes the answer.

int[] numbers = { 2, 3, 4 };
int product = numbers.Aggregate(0, (a, b) => a * b); // Always 0

Use 1 as the multiplication seed:

int product = numbers.Aggregate(1, (a, b) => a * b); // 24

Forgetting to return the accumulator

The accumulator function must return the next accumulator value.

// Does not compile: the lambda has no returned value.
// numbers.Aggregate(0, (total, number) => { total += number; });

Return the value:

int total = numbers.Aggregate(0, (total, number) =>
{
    total += number;
    return total;
});

Using Aggregate when a built-in method is clearer

int total = numbers.Aggregate(0, (a, b) => a + b);

This is valid, but the following communicates intent more directly:

int total = numbers.Sum();

Assuming the source collection changes

Aggregate returns a result. It does not replace or modify the source sequence itself. If the accumulator is a mutable object, that accumulator may be modified, but the source items are not automatically changed.

Comparisons

TaskPreferred APIWhy
Add numeric valuesSumDirect and readable
Find a minimum or maximumMin / MaxExpresses the operation clearly
Test whether all items matchAllCan stop as soon as an item fails
Test whether any item matchesAnyCan stop as soon as an item matches
Join stringsstring.JoinHandles separators cleanly
Build a custom single resultAggregateLets you control the accumulator logic

Aggregate without a seed uses the first source item as its initial value. Aggregate with a seed is safer when the sequence may be empty and is required when the accumulator has a different type from the source items.

string[] words = { "a", "bb", "ccc" };

int totalLength = words.Aggregate(
    0,
    (total, word) => total + word.Length);

Here, source items are string, but the accumulator is an int, so a seed is necessary.

Take Quiz

Cheat Sheet

using System.Linq;

// No seed: first element starts the accumulator.
var result = source.Aggregate((acc, item) => Combine(acc, item));

// Seed: use this for empty sequences or a different accumulator type.
var result = source.Aggregate(seed, (acc, item) => Combine(acc, item));

// Result selector: transform the final accumulator.
var result = source.Aggregate(
    seed,
    (acc, item) => Combine(acc, item),
    acc => Transform(acc));
  • acc is the running result.
  • item is the current source element.
  • The accumulator lambda must return the next acc value.
  • No-seed overload + empty source = InvalidOperationException.
  • Seeded overload + empty source = returns the seed, or the result selector applied to the seed.
  • Common identity seeds: 0 for addition, 1 for multiplication, "" for text.
  • Prefer Sum, Min, Max, All, Any, and string.Join when they exactly express the task.

FAQ

What does LINQ Aggregate do in C#?

It reduces every item in a sequence into one final value by repeatedly applying an accumulator function.

What is the accumulator in Aggregate?

It is the running result. Each callback receives the current accumulator and the next source item, then returns the updated accumulator.

What is a seed value in LINQ Aggregate?

A seed is the initial accumulator value. It lets Aggregate handle empty sequences and lets the accumulator type differ from the item type.

Does Aggregate work with an empty list?

The overload without a seed throws on an empty sequence. A seeded overload returns the seed value, or applies its result selector to that seed.

Should I use Aggregate to add numbers?

Usually no. Prefer Sum() because it is shorter and communicates the intent more clearly.

What is the difference between Aggregate and Select?

Select transforms each item and returns a sequence of transformed items. Aggregate combines all items and returns one result.

Can Aggregate create an object or dictionary?

Yes. Seed it with the object or dictionary, update it for each item, and return it from the accumulator function.

Take Quiz

Mini Project

Description

Create a small order-summary calculator. Given a collection of order lines, use Aggregate to calculate the number of units purchased and the total cost. This resembles the summary shown in a shopping cart or invoice.

Goal

Produce one OrderSummary object containing the total quantity and total price for all order lines.

Requirements

Use an OrderLine type with product name, unit price, and quantity.

Create at least three order lines.

Use Aggregate with an OrderSummary seed.

Add each line's quantity and line total to the summary.

Print the final unit count and total cost.

Take Quiz

Keep learning

Related questions

View more