Question

How to Use GroupBy in LINQ to Group Objects and Collect Values in C#

csharplinqgroup-by

Question

You have a C# class like this:

class Person
{
    internal int PersonID;
    internal string car;
}

And a list of Person objects:

List<Person> persons;

This list may contain multiple items with the same PersonID, for example:

persons[0] = new Person { PersonID = 1, car = "Ferrari" };
persons[1] = new Person { PersonID = 1, car = "BMW" };
persons[2] = new Person { PersonID = 2, car = "Audi" };

How can you use LINQ to group these items by PersonID and get a list of all cars belonging to each person?

For example, you want a result shape like this:

class Result
{
    int PersonID;
    List<string> cars;
}

So after grouping, the result would contain one item per PersonID, where each item stores that person's ID and a list of their car names.

You started with:

var results = from p in persons
              group p by p.PersonID into g
              select new { PersonID = g.Key };

What should be added so that each grouped result also contains the list of car names?

Short Answer

By the end of this page, you will understand how GroupBy works in LINQ, how to group objects by a key such as PersonID, and how to project each group into a new result object containing a list of values like car names.

Concept

GroupBy in LINQ is used when you want to organize a sequence into buckets based on a shared key.

In this example, the key is PersonID. Every Person object with the same PersonID should end up in the same group.

Once the data is grouped, each group gives you:

  • g.Key — the value used for grouping, such as 1 or 2
  • g — the collection of items inside that group

That means you can do two things after grouping:

  • keep the key (PersonID)
  • transform the grouped items into something else, such as a List<string> of car names

This matters in real programming because grouped data appears everywhere:

  • orders grouped by customer
  • messages grouped by conversation
  • products grouped by category
  • logs grouped by date or severity

LINQ makes this kind of transformation concise and readable.

Mental Model

Think of GroupBy like sorting papers into folders.

  • The folder label is the grouping key, such as PersonID
  • Every Person record goes into the folder matching its ID
  • After that, you open each folder and pull out just the information you need, such as the car names

So for this data:

  • Person 1 has Ferrari and BMW
  • Person 2 has Audi

You end up with two folders:

  • Folder 1 contains two cars
  • Folder 2 contains one car
Take Quiz

Syntax and Examples

Basic GroupBy syntax

Query syntax

var results = from p in persons
              group p by p.PersonID into g
              select new
              {
                  PersonID = g.Key,
                  Cars = g.Select(x => x.car).ToList()
              };

Method syntax

var results = persons
    .GroupBy(p => p.PersonID)
    .Select(g => new
    {
        PersonID = g.Key,
        Cars = g.Select(x => x.car).ToList()
    });

Using a named result class

A real program often uses a named class instead of an anonymous object:

class Result
{
    public int PersonID { get; set; }
    public List<string> Cars { get; set; }
}

var results = persons
    .GroupBy(p => p.PersonID)
    .Select(g => new Result
    {
        PersonID = g.Key,
        Cars = g.Select(x => x.car).ToList()
    })
    .ToList();

Example input and output

var persons = new List<Person>
{
    new Person { PersonID = 1, car = "Ferrari" },
    new Person { PersonID = 1, car = "BMW" },
    new Person { PersonID = 2, car = "Audi" }
};

Result:

  • PersonID = 1, Cars = ["Ferrari", "BMW"]
  • PersonID = 2, Cars = ["Audi"]

Why g.Select(x => x.car).ToList()?

  • g is the whole group of Person objects
  • Select(x => x.car) extracts only the car value from each object
  • ToList() converts the sequence into a List<string>

Step by Step Execution

Consider this code:

var results = persons
    .GroupBy(p => p.PersonID)
    .Select(g => new Result
    {
        PersonID = g.Key,
        Cars = g.Select(x => x.car).ToList()
    })
    .ToList();

And this input:

var persons = new List<Person>
{
    new Person { PersonID = 1, car = "Ferrari" },
    new Person { PersonID = 1, car = "BMW" },
    new Person { PersonID = 2, car = "Audi" }
};

Step 1: GroupBy(p => p.PersonID)

LINQ scans each item and groups them by PersonID.

It creates groups like:

  • Group key 1 -> [Ferrari, BMW]
  • Group key 2 -> [Audi]

More precisely:

  • Group 1 contains two Person objects
  • Group 2 contains one Person object

Step 2: .Select(g => new Result { ... })

LINQ transforms each group into one Result object.

For the first group:

new Result
{
    PersonID = 1,
    Cars = new List<string> { "Ferrari", "BMW" }
}

For the second group:

new Result
{
    PersonID = 2,
    Cars = new List<string> { "Audi" }
}

Step 3: .ToList()

The final sequence of Result objects is materialized into a List<Result>.

So results becomes a list containing one item per person ID.

Real World Use Cases

Grouping with LINQ is common in many kinds of applications.

Common examples

  • E-commerce: group orders by customer ID
  • Chat apps: group messages by conversation ID
  • School systems: group students by class or department
  • Analytics: group events by day, user, or event type
  • Inventory systems: group products by category
  • Finance apps: group transactions by account

Example: orders by customer

var groupedOrders = orders
    .GroupBy(o => o.CustomerId)
    .Select(g => new
    {
        CustomerId = g.Key,
        OrderIds = g.Select(o => o.Id).ToList()
    });

Example: logs by severity

var groupedLogs = logs
    .GroupBy(log => log.Level)
    .Select(g => new
    {
        Level = g.Key,
        Messages = g.Select(log => log.Message).ToList()
    });

The same idea is used in your PersonID and car example.

Take Quiz

Real Codebase Usage

In real projects, developers often combine GroupBy with projection and cleanup steps.

Common patterns

Group and project into DTOs

Instead of keeping raw grouped objects, developers usually create a result type:

var results = persons
    .GroupBy(p => p.PersonID)
    .Select(g => new Result
    {
        PersonID = g.Key,
        Cars = g.Select(p => p.car).ToList()
    })
    .ToList();

This is cleaner for APIs, services, and UI models.

Remove duplicates inside groups

If one person might have the same car listed multiple times:

var results = persons
    .GroupBy(p => p.PersonID)
    .Select(g => new Result
    {
        PersonID = g.Key,
        Cars = g.Select(p => p.car).Distinct().ToList()
    })
    .ToList();

Filter before grouping

You may only want valid rows:

var results = persons
    .Where(p => !string.IsNullOrWhiteSpace(p.car))
    .GroupBy(p => p.PersonID)
    .Select(g => new Result
    {
        PersonID = g.Key,
        Cars = g.Select(p => p.car).ToList()
    })
    .ToList();

Order groups or values

var results = persons
    .GroupBy(p => p.PersonID)
    .OrderBy(g => g.Key)
    .Select(g => new Result
    {
        PersonID = g.Key,
        Cars = g.Select(p => p.car).OrderBy(car => car).ToList()
    })
    .ToList();

These patterns make grouped results safer and easier to use.

Common Mistakes

1. Forgetting to select the property you actually want

Broken code:

var results = persons
    .GroupBy(p => p.PersonID)
    .Select(g => new Result
    {
        PersonID = g.Key,
        Cars = g.ToList()
    });

Problem:

  • g.ToList() returns a List<Person>
  • but Cars expects a List<string>

Fix:

Cars = g.Select(x => x.car).ToList()

2. Confusing the group key with group items

Broken thinking:

  • g.Key is the ID
  • g is the collection of all matching items

Do not try to get car names from g.Key.

3. Forgetting ToList() when a List<string> is required

Broken code:

Cars = g.Select(x => x.car)

This returns IEnumerable<string>, not List<string>.

Fix:

Cars = g.Select(x => x.car).ToList()

4. Using fields instead of properties in result models

Fields can work, but properties are more common and easier to use with serializers, APIs, and frameworks.

Better:

class Result
{
    public int PersonID { get; set; }
    public List<string> Cars { get; set; }
}

5. Not handling null or empty values

If car can be null, your grouped output may contain null entries.

Safer version:

Cars = g.Where(x => !string.IsNullOrWhiteSpace(x.car))
        .Select(x => x.car)
        .ToList()

Comparisons

GroupBy vs related LINQ operations

ConceptWhat it doesWhen to use it
SelectTransforms each itemWhen you want one output per input item
WhereFilters itemsWhen you want to remove items that do not match a condition
GroupByBuckets items by a keyWhen you want one output per group
ToDictionaryBuilds key-value lookup dataWhen keys must be unique in the final result
DistinctRemoves duplicatesWhen you only need unique values, not grouped collections

Query syntax vs method syntax

StyleExampleNotes
Query syntaxfrom p in persons group p by p.PersonID into g select ...Reads more like SQL
Method syntaxpersons.GroupBy(...).Select(...)Very common in C# codebases

Anonymous type vs named class

OptionExampleBest for
Anonymous typenew { PersonID = g.Key, Cars = ... }Quick local use
Named classnew Result { PersonID = g.Key, Cars = ... }Reusable app code, APIs, testing

If you need the result outside a small local scope, a named class is usually better.

Take Quiz

Cheat Sheet

// Group by key and collect values
var results = persons
    .GroupBy(p => p.PersonID)
    .Select(g => new Result
    {
        PersonID = g.Key,
        Cars = g.Select(p => p.car).ToList()
    })
    .ToList();

Key ideas

  • GroupBy(p => p.PersonID) groups items by PersonID
  • g.Key gives the group key
  • g contains all items in that group
  • g.Select(p => p.car) extracts car names
  • ToList() converts the sequence to List<string>

Query syntax version

var results = from p in persons
              group p by p.PersonID into g
              select new Result
              {
                  PersonID = g.Key,
                  Cars = g.Select(x => x.car).ToList()
              };

Useful variations

Distinct values

Cars = g.Select(x => x.car).Distinct().ToList()

Ignore null or blank values

Cars = g.Where(x => !string.IsNullOrWhiteSpace(x.car))
        .Select(x => x.car)
        .ToList()

Sort values

Cars = g.Select(x => x.car).OrderBy(car => car).ToList()

FAQ

How do I get the list of values from each LINQ group?

Use Select on the group itself. For example, g.Select(x => x.car).ToList() extracts all car names from that group.

What does g.Key mean in LINQ GroupBy?

g.Key is the value used for grouping. In this case, it is the PersonID.

Can I group by one property and select another in LINQ?

Yes. You can group by PersonID and then select car, name, date, or any other property from the grouped items.

Should I use query syntax or method syntax for GroupBy?

Both are valid. Method syntax is more common in many C# codebases, but query syntax can be easier to read for some learners.

How do I remove duplicate cars inside each group?

Use Distinct() before ToList():

Cars = g.Select(x => x.car).Distinct().ToList()

Can GroupBy return a custom class?

Yes. After grouping, use Select to create instances of your own class, such as Result.

Why is my grouped result not a List?

LINQ usually returns IEnumerable<T> until you materialize it with .ToList() or .ToArray().

Take Quiz

Mini Project

Description

Build a small C# console example that groups people by PersonID and prints the list of cars owned by each person. This demonstrates how to transform repeated records into grouped output that is easier to use in reports, APIs, or UI screens.

Goal

Create a grouped list where each person ID appears once and contains all associated car names.

Requirements

  • Create a Person class with PersonID and car
  • Create a Result class with PersonID and List<string> Cars
  • Add sample data with repeated PersonID values
  • Use LINQ GroupBy to build the grouped result
  • Print each person ID and their cars to the console
Take Quiz

Keep learning

Related questions

View more