CodeGym /Courses /C# SELF /Grouping Data with group by<...

Grouping Data with group by

C# SELF
Level 32, Lesson 0
Available

1. Introduction

Grouping data is all about splitting up elements of a collection into "buckets" based on some shared feature. Imagine you're sorting apples by color: all the red ones go in one basket, green ones in another, yellow ones somewhere else. In programming, that's called grouping.

Why do we need it?
In real-world tasks, it's super useful to group people by city, products by category, transactions by date, and so on. This lets you do cool analytics: like, find out how many students are in each class, or which cities are the most student-packed.

LINQ gives you a special method for this: GroupBy. There's also a handy operator in query syntax — that's group by. Let's break down how this works with examples from our student and class tracking app.

2. GroupBy in method syntax

Main signature and what it returns

The GroupBy method looks a little scary:


IEnumerable<IGrouping<TKey, TElement>> GroupBy<TElement, TKey>(
    this IEnumerable<TElement> source,
    Func<TElement, TKey> keySelector
)

Where:

  • source — the original collection (like a list of students).
  • keySelector — a function that picks the grouping feature, like the ClassName or City property.

Heads up!

The result isn't just an array of your objects, but a collection of special groups ( IGrouping<TKey, TElement>). Each group has:
  • The group key (Key — what the elements have in common),
  • A collection of elements that landed in that group.

Diagram:

Key Group elements (group of objects)
"9A" Ivan, Oleg, Maria
"10B" Svetlana, Pyotr
... ...

Example 1: Grouping students by class name

Let's say we have a Student class:


public class Student
{
    public string Name { get; set; }
    public string ClassName { get; set; }
    public int Grade { get; set; }
}

And a collection of students:


var students = new List<Student>
{
    new Student { Name = "Ivan", ClassName = "9A", Grade = 5 },
    new Student { Name = "Oleg", ClassName = "9A", Grade = 4 },
    new Student { Name = "Maria", ClassName = "10B", Grade = 5 },
    new Student { Name = "Svetlana", ClassName = "10B", Grade = 3 }
};

Let's group students by class:


var studentsByClass = students.GroupBy(s => s.ClassName);

foreach (var group in studentsByClass)
{
    Console.WriteLine($"Class: {group.Key}"); // Group key
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}, grade: {student.Grade}");
    }
}

What's going on?
The GroupBy method made two groups for us: one for "9A", another for "10B". Looping through the groups, we can print out any aggregated info about each "bunch".

Visualization: what the collection looks like after GroupBy

Think of it like this:


students.GroupBy(s => s.ClassName)
        ├─ group "9A" { Ivan, Oleg }
        └─ group "10B" { Maria, Svetlana }

Each "branch" is a mini-list of students, grouped by the key.

Students by grade: grouping by any feature

Let's group by grades instead of classes!


var studentsByGrade = students.GroupBy(s => s.Grade);

foreach (var group in studentsByGrade)
{
    Console.WriteLine($"Grade: {group.Key}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name} from class {student.ClassName}");
    }
}

Output:


Grade: 5
 - Ivan from class 9A
 - Maria from class 10B
Grade: 4
 - Oleg from class 9A
Grade: 3
 - Svetlana from class 10B

3. Working with groups in LINQ: what you can do after GroupBy

A lot of times after grouping, you want more than just the groups — you want some aggregated info, like counting elements in each group, getting the average grade, or collecting a list of names.

Example 2: Counting students in each class


var classCounts = students
    .GroupBy(s => s.ClassName)
    .Select(g => new { Class = g.Key, Count = g.Count() });

foreach (var cc in classCounts)
{
    Console.WriteLine($"Class {cc.Class} — {cc.Count} students");
}

Result:


Class 9A — 2 students
Class 10B — 2 students
  • First, we split students into groups.
  • Then for each group, we made an anonymous object with the key (class name) and the count (Count() method).

Example 3: Get a list of honor students by class


var excellentByClass = students
    .Where(s => s.Grade == 5)
    .GroupBy(s => s.ClassName)
    .Select(g => new 
    {
        Class = g.Key,
        ExcellentStudents = g.Select(s => s.Name).ToList()
    });

foreach (var group in excellentByClass)
{
    Console.WriteLine($"In class {group.Class} honor students: {string.Join(", ", group.ExcellentStudents)}");
}

4. group by in query syntax

If you're craving something more SQL-like, or you come from a database background, LINQ supports query syntax like group by ... into ....

Basic grouping example


var studentsByClass =
    from s in students
    group s by s.ClassName into classGroup
    select classGroup;

foreach (var group in studentsByClass)
{
    Console.WriteLine($"Class: {group.Key}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}");
    }
}

First we "group" by the feature, then give the group a name (into classGroup) — and then you can do whatever you want with it.

Projection with aggregates

Say we want to know the classes and how many students are in each:


var countsByCity =
    from s in students
    group s by s.ClassName into classGroup
    select new { Class = classGroup.Key, Count = classGroup.Count() };

foreach (var item in countsByCity)
{
    Console.WriteLine($"Class {item.Class} — {item.Count} students");
}

— The syntax is the same, but after select you can do any projection (aggregation, lists, averages, etc.).

5. Extra scenarios

Grouping by multiple features (composite key)

Sometimes you need to group by a combo of properties. For example, by class and grade:


var byClassAndGrade = students
    .GroupBy(s => new { s.ClassName, s.Grade });

foreach (var group in byClassAndGrade)
{
    Console.WriteLine($"Class: {group.Key.ClassName}, Grade: {group.Key.Grade}");
    foreach (var student in group)
    {
        Console.WriteLine($" - {student.Name}");
    }
}

The group key is now an anonymous type (a combo of several fields).

How do groups work in practice?

A bit of LINQ internals

  • When you do GroupBy, LINQ builds a special "table" inside: for every unique key value — a separate group.
  • Groups implement the IGrouping<TKey, TElement> interface. You can use the Key property to get the value you grouped by.

Lazy evaluation and performance

  • Groups aren't built right away, but only when you start looping through them (foreach). So if you have huge collections — don't worry about "creating" groups until you actually use them, they only exist when you need them.
  • If you plan to access groups multiple times — you can call .ToList() or .ToArray() to "lock in" the result.

Real-world use

Grouping lets you build complex reports, stats, and analytics. Here are some real examples where it comes in handy:

  • In an online store: group orders by user to show each person's purchase history.
  • In an education platform: group students by teacher or by subject.
  • In an HR app: group employees by department and count the headcount.
  • At an interview: you might be asked to write a grouping of real data with value aggregation.

LINQ lets you prototype stuff like this super fast, and being able to read and write groupings is a big plus for any .NET dev.

Visual diagram

Here's a simple block diagram of processing a collection with GroupBy:


[Student collection] 
      |
[GroupBy by ClassName]
      |
[Group "9A"] ---> [Ivan, Oleg]
[Group "10B"] --> [Maria, Svetlana]

Each group is its own mini-collection with a key.

6. Common mistakes and gotchas

Some students get tripped up at first because of how GroupBy returns stuff:

  • After calling GroupBy you don't just get "groups" like List<List<Student>>, you get a collection of IGrouping<TKey, TElement>. To work with groups, use the Key property and loop through the group's elements.
  • Grouping by a complex key (multiple fields): don't forget to use an anonymous type or make a separate class if you need a structured key.
  • If you just want the count of elements in groups — don't forget to use Select(g => g.Count()), otherwise you'll have to count elements inside each group manually.
  • Sometimes it's easy to get lost in nested loops: first loop over groups, second — over elements inside the group.
  • If you mess up the lambda or key, you'll get groups you didn't expect (or one big group if the key is the same for everyone).
2
Task
C# SELF, level 32, lesson 0
Locked
Monthly Sales Report
Monthly Sales Report
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION