How to Use Group By in C# LINQ

This article explains the group by operation in C# Language Integrated Query (LINQ) and how to effectively process grouped data. You will learn what group by does, understand the underlying IGrouping<TKey, TElement> structure, explore both query syntax and method syntax, and see how to iterate over, project, aggregate, and group by multiple keys.

Understanding Group By in LINQ

The group by operator reorganizes collections into sequences of buckets based on a specified key. Instead of returning a flat list of items, LINQ grouping returns an IEnumerable<IGrouping<TKey, TElement>>.

An IGrouping<TKey, TElement> is essentially a collection with a Key property:

  • Key: The shared value that defines the group.
  • Elements: The items from the source collection that match the key.

Basic Grouping Example

Consider an Employee class:

public record Employee(string Name, string Department, decimal Salary);

var employees = new List<Employee>
{
    new("Alice", "IT", 75000),
    new("Bob", "HR", 50000),
    new("Charlie", "IT", 80000),
    new("Diana", "Finance", 90000),
    new("Evan", "HR", 55000)
};

Method Syntax

var groupedByDept = employees.GroupBy(e => e.Department);

Query Syntax

var groupedByDept = from e in employees
                    group e by e.Department;

How to Work with Grouping Results

Because each group contains both a key and an inner collection, you typically process the results using nested loops or projection.

Iterating Over Groups

To read grouped data, loop through each group to access the key, then loop through the group's elements:

foreach (var group in groupedByDept)
{
    Console.WriteLine($"Department: {group.Key}");
    foreach (var employee in group)
    {
        Console.WriteLine($" - {employee.Name} (${employee.Salary})");
    }
}

Projecting and Aggregating Results

Instead of working with raw IGrouping instances, you often project the results into an anonymous type or a Data Transfer Object (DTO) combined with aggregate functions like Count(), Sum(), or Average().

Method Syntax

var departmentSummaries = employees
    .GroupBy(e => e.Department)
    .Select(g => new
    {
        Department = g.Key,
        TotalEmployees = g.Count(),
        TotalSalary = g.Sum(e => e.Salary),
        AverageSalary = g.Average(e => e.Salary)
    });

foreach (var dept in departmentSummaries)
{
    Console.WriteLine($"{dept.Department}: Count = {dept.TotalEmployees}, Avg Salary = {dept.AverageSalary:C}");
}

Query Syntax

In query syntax, use the into keyword to store grouping results into a continuation variable before projecting:

var departmentSummaries = from e in employees
                          group e by e.Department into g
                          select new
                          {
                              Department = g.Key,
                              TotalEmployees = g.Count(),
                              TotalSalary = g.Sum(e => e.Salary)
                          };

Grouping by Multiple Keys

To group by more than one property, group by an anonymous object. LINQ compares anonymous objects based on property values rather than reference equality.

var groupedByDeptAndSalaryTier = employees
    .GroupBy(e => new 
    { 
        e.Department, 
        IsHighEarner = e.Salary >= 75000 
    })
    .Select(g => new
    {
        g.Key.Department,
        g.Key.IsHighEarner,
        Employees = g.Select(e => e.Name).ToList()
    });

Selecting Custom Elements in Groups

If you only need specific properties within each group rather than the entire source object, provide an element selector parameter to GroupBy:

// Groups only employee names by department
var namesByDept = employees.GroupBy(
    e => e.Department,  // Key selector
    e => e.Name         // Element selector
);

foreach (var group in namesByDept)
{
    Console.WriteLine($"{group.Key}: {string.Join(", ", group)}");
}