How to use LINQ GroupBy in C#

The LINQ GroupBy() method organizes elements into groups based on a key selector. It returns an IEnumerable<IGrouping<TKey, TElement>> where each IGrouping holds the key and the matching elements.

Each group is itself an IEnumerable<T>, so you can apply further LINQ operations (Count, Sum, Average, etc.) to each group.

GroupBy() uses deferred execution. The grouping materializes when you iterate, call .ToList(), or perform an aggregation.

Basic GroupBy

C# Example Code
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Group into even and odd
var groups = numbers.GroupBy(n => n % 2 == 0 ? "Even" : "Odd");

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

// Output:
// Odd:  1, 3, 5, 7, 9
// Even: 2, 4, 6, 8, 10

Group Objects by Property

C# Example Code
var people = new List<Person>
{
    new Person("Alice",   "Engineering"),
    new Person("Bob",     "Marketing"),
    new Person("Carol",   "Engineering"),
    new Person("Dave",    "Marketing"),
    new Person("Eve",     "Engineering")
};

var byDepartment = people.GroupBy(p => p.Department);

foreach (var dept in byDepartment)
{
    Console.WriteLine($"\n{dept.Key}:");
    foreach (var person in dept)
        Console.WriteLine($"  - {person.Name}");
}

// Output:
// Engineering:
//   - Alice
//   - Carol
//   - Eve
// Marketing:
//   - Bob
//   - Dave

record Person(string Name, string Department);

Count per Group

C# Example Code
var orders = new List<Order>
{
    new Order("Alice", "Electronics"),
    new Order("Bob",   "Books"),
    new Order("Alice", "Books"),
    new Order("Bob",   "Electronics"),
    new Order("Carol", "Electronics")
};

// Count orders per customer
var orderCounts = orders
    .GroupBy(o => o.Customer)
    .Select(g => new { Customer = g.Key, Count = g.Count() })
    .OrderByDescending(x => x.Count);

foreach (var row in orderCounts)
    Console.WriteLine($"{row.Customer}: {row.Count} orders");

// Output:
// Alice: 2 orders
// Bob: 2 orders
// Carol: 1 orders

record Order(string Customer, string Category);

Aggregate Within Groups (Sum, Average, Max)

C# Example Code
var sales = new List<Sale>
{
    new Sale("North", 1500m),
    new Sale("South", 2200m),
    new Sale("North", 800m),
    new Sale("East",  3100m),
    new Sale("South", 950m),
    new Sale("North", 600m)
};

var regionStats = sales
    .GroupBy(s => s.Region)
    .Select(g => new
    {
        Region  = g.Key,
        Total   = g.Sum(s => s.Amount),
        Average = g.Average(s => s.Amount),
        Max     = g.Max(s => s.Amount),
        Count   = g.Count()
    })
    .OrderByDescending(r => r.Total);

foreach (var r in regionStats)
    Console.WriteLine($"{r.Region}: Total={r.Total:C0}, Avg={r.Average:C0}, Max={r.Max:C0}, Count={r.Count}");

// Output:
// North: Total=$2,900, Avg=$967, Max=$1,500, Count=3
// East:  Total=$3,100, Avg=$3,100, Max=$3,100, Count=1
// South: Total=$3,150, Avg=$1,575, Max=$2,200, Count=2

record Sale(string Region, decimal Amount);

Group by Multiple Keys

C# Example Code
var employees = new List<Employee>
{
    new Employee("Alice", "Engineering", "Senior"),
    new Employee("Bob",   "Marketing",   "Junior"),
    new Employee("Carol", "Engineering", "Junior"),
    new Employee("Dave",  "Marketing",   "Senior"),
    new Employee("Eve",   "Engineering", "Senior")
};

// Group by department + level using an anonymous type key
var grouped = employees
    .GroupBy(e => new { e.Department, e.Level })
    .Select(g => new
    {
        g.Key.Department,
        g.Key.Level,
        Names = string.Join(", ", g.Select(e => e.Name)),
        Count = g.Count()
    });

foreach (var g in grouped)
    Console.WriteLine($"{g.Department} / {g.Level}: {g.Names} ({g.Count})");

// Output:
// Engineering / Senior: Alice, Eve (2)
// Marketing / Junior:   Bob (1)
// Engineering / Junior: Carol (1)
// Marketing / Senior:   Dave (1)

record Employee(string Name, string Department, string Level);

Method Syntax vs Query Syntax

C# Example Code
var people = new List<Person>
{
    new Person("Alice", "Engineering"),
    new Person("Bob",   "Marketing"),
    new Person("Carol", "Engineering")
};

// Method syntax
var methodGroups = people
    .GroupBy(p => p.Department)
    .Select(g => new { Dept = g.Key, Count = g.Count() });

// Query syntax
var queryGroups = from p in people
                  group p by p.Department into g
                  select new { Dept = g.Key, Count = g.Count() };

GroupBy vs ToLookup

Both group elements by a key, but they differ in when they execute and how you access the result.

C# Example Code
var people = new List<Person>
{
    new Person("Alice", "Engineering"),
    new Person("Bob",   "Marketing"),
    new Person("Carol", "Engineering")
};

// GroupBy — deferred execution, enumerates source once per group
var groupBy = people.GroupBy(p => p.Department);

// ToLookup — immediate execution, O(1) key lookup
var lookup = people.ToLookup(p => p.Department);

// Access by key (safe — returns empty sequence if key missing)
foreach (var person in lookup["Engineering"])
    Console.WriteLine(person.Name);

// Output:
// Alice
// Carol
GroupBy()ToLookup()
ExecutionDeferredImmediate
Access by keyIterate groupsO(1) index lookup
Missing keySkippedEmpty sequence returned
Use caseLINQ pipelineRepeated lookups on same data