100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Grouping with GroupBy

Learn how LINQ's GroupBy operator clusters elements by a key into IGrouping sequences, including result selectors, composite keys, and how it differs from SQL's GROUP BY.

Core OperatorsIntermediate10 min readJul 10, 2026
Analogies

Grouping Elements with GroupBy

GroupBy() partitions a sequence into clusters sharing a common key, returning an IEnumerable<IGrouping<TKey, TElement>> — each IGrouping<TKey, TElement> is itself an IEnumerable<TElement> that also exposes a Key property, so you can iterate the groups and, within each, iterate its members: foreach (var group in orders.GroupBy(o => o.CustomerId)) { Console.WriteLine(group.Key); foreach (var order in group) { ... } }. Unlike Where() or Select(), which process one element at a time, GroupBy() must buffer enough of the source to know which elements share a key before it can yield the first complete group.

🏏

Cricket analogy: Organizing a season's match results into clusters by team — every India match together, every Australia match together — mirrors GroupBy(match => match.Team), where each cluster's key is the team name.

Grouping with a Result Selector

GroupBy() has overloads accepting a resultSelector that runs once per group, transforming the (key, elements) pair directly into a final shape rather than requiring a separate Select() afterward: orders.GroupBy(o => o.CustomerId, (key, group) => new { CustomerId = key, Total = group.Sum(o => o.Amount) }). This is functionally equivalent to GroupBy(keySelector).Select(g => ...) but avoids materializing the intermediate IGrouping objects into a separately-selected sequence, which can be marginally more efficient and often reads more directly.

🏏

Cricket analogy: A tournament summary that groups matches by team and immediately computes each team's total wins, rather than listing raw match groups first, mirrors GroupBy() with a result selector producing the final shape in one step.

Grouping by Composite Keys

When a single field isn't enough to define a group, you can group by an anonymous type combining multiple fields: orders.GroupBy(o => new { o.CustomerId, o.Status }) creates one group per unique (CustomerId, Status) combination. Anonymous types automatically implement value-based equality — two anonymous type instances with the same property values in the same order are considered equal — which is exactly the equality semantics GroupBy() needs to correctly cluster elements by a composite key.

🏏

Cricket analogy: Grouping innings by both team AND venue, so 'India at Lord's' is a distinct group from 'India at the MCG', mirrors GroupBy(inning => new { inning.Team, inning.Venue }) using a composite key.

GroupBy vs. SQL GROUP BY

LINQ-to-Objects' GroupBy() differs meaningfully from SQL's GROUP BY: SQL requires every selected column to be either part of the GROUP BY clause or wrapped in an aggregate function, but LINQ's GroupBy() gives you the full member sequence inside each IGrouping, so you can access every original field of every element in a group, not just aggregated summaries. When GroupBy() runs against an IQueryable (e.g., Entity Framework), the provider must translate it into an equivalent SQL GROUP BY, which means accessing anything beyond the key or an aggregate inside the group can force the provider to either restructure the query or throw a translation exception.

🏏

Cricket analogy: SQL GROUP BY is like a scoreboard that only shows each team's total runs, while LINQ's GroupBy() is like having the full ball-by-ball log still accessible within each team's group, letting you inspect any individual delivery.

csharp
var orders = new List<Order>
{
    new Order { Id = 1, CustomerId = 100, Status = "Shipped", Amount = 50.00m },
    new Order { Id = 2, CustomerId = 100, Status = "Pending", Amount = 30.00m },
    new Order { Id = 3, CustomerId = 200, Status = "Shipped", Amount = 75.00m },
};

// Basic grouping: one IGrouping per CustomerId
foreach (var group in orders.GroupBy(o => o.CustomerId))
{
    Console.WriteLine($"Customer {group.Key}: {group.Count()} orders");
    foreach (var order in group)
        Console.WriteLine($"  Order {order.Id}: {order.Amount:C}");
}

// Result selector: group + aggregate in one step
var customerTotals = orders.GroupBy(
    o => o.CustomerId,
    (key, group) => new { CustomerId = key, Total = group.Sum(o => o.Amount) });

// Composite key grouping
var byCustomerAndStatus = orders.GroupBy(o => new { o.CustomerId, o.Status });

GroupBy() does not sort its output groups or the elements within each group — groups appear in the order their key was first encountered in the source, and elements within a group retain their original relative order. If you need sorted groups or sorted members within a group, chain OrderBy() explicitly after GroupBy().

Against Entity Framework and other IQueryable providers, GroupBy() followed by operations that touch non-aggregated, non-key columns inside the group (like group.Select(o => o.CustomerName)) may either translate into a client-evaluated fallback (pulling more data than expected) or throw a translation exception entirely, depending on the provider version. When working against a database, keep post-GroupBy() projections limited to the key and standard aggregate functions (Sum, Count, Average, Min, Max) to stay safely within what SQL's GROUP BY can express.

  • GroupBy() partitions a sequence into IGrouping<TKey, TElement> clusters, each exposing a Key and its own enumerable members.
  • The result-selector overload combines grouping and transformation in a single pass, avoiding a separate Select().
  • Composite keys use anonymous types, which have value-based equality suited to GroupBy()'s clustering needs.
  • Unlike SQL's GROUP BY, LINQ's GroupBy() keeps full access to every original element within each group, not just aggregates.
  • Against IQueryable/database providers, non-aggregate access inside groups can fail to translate or force client evaluation.
  • GroupBy() preserves first-encountered key order for groups and original relative order within each group.
  • GroupBy() must buffer enough source data to determine group membership before yielding results.

Practice what you learned

Was this page helpful?

Topics covered

#LINQStudyNotes#MicrosoftTechnologies#GroupingWithGroupBy#Grouping#GroupBy#Elements#Result#StudyNotes#SkillVeris#ExamPrep