Advanced LINQ Queries & Optimization
Lesson 15 • Advanced Track
GroupBy, SelectMany, deferred execution, and LINQ performance tuning for production C#.
What You'll Learn
- • Use
GroupByto aggregate and summarise data - • Flatten nested collections with
SelectMany - • Understand deferred execution — when LINQ queries actually run
- • Avoid the multiple-enumeration trap with
ToList() - • Optimise LINQ pipelines: filter early, project late, limit results
- • Choose
Any()overCount() > 0for performance
Real-World Analogy
GroupBy is like sorting email into folders by sender. SelectMany is like emptying all folders onto one desk — flattening groups into a single pile. Deferred execution is like writing a shopping list (the query) vs going to the shops (iterating).
Running C# Locally: Install the .NET SDK or use dotnetfiddle.net.
📋 Advanced LINQ Methods Reference
| Method | Returns | Use Case |
|---|---|---|
| GroupBy() | IGrouping | Group and aggregate items |
| SelectMany() | Flat sequence | Flatten nested collections |
| ToDictionary() | Dictionary | O(1) lookups by key |
| ToLookup() | ILookup | Materialised grouping |
| Aggregate() | Single value | Custom reduction |
| Chunk() | Batched arrays | Batch processing (.NET 6+) |
GroupBy — Aggregate & Summarise
GroupBy splits a collection into groups based on a key. Each group has a .Key and contains all matching items. Chain with .Select() to build summary objects.
GroupBy — Order Reports
Group orders by customer and category with Sum, Count, and Average.
using System;
using System.Linq;
using System.Collections.Generic;
class Order
{
public string Customer { get; set; }
public string Product { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
class Program
{
static void Main()
{
var orders = new List<Order>
{
new Order { Customer = "Alice", Product = "Laptop", Price = 999, Category = "Electronics" },
new Order { Customer = "Bob", Product = "Keyb
...SelectMany — Flatten Nested Data
Select maps each item — if it's a list, you get a list of lists. SelectMany flattens those nested lists into a single sequence. Essential for departments→employees, posts→tags.
SelectMany — Flatten Departments
Flatten employee lists from multiple departments into one sequence.
using System;
using System.Linq;
using System.Collections.Generic;
class Department
{
public string Name { get; set; }
public List<string> Employees { get; set; }
}
class Program
{
static void Main()
{
var departments = new List<Department>
{
new Department { Name = "Engineering", Employees = new List<string> { "Alice", "Bob", "Carol" } },
new Department { Name = "Marketing", Employees = new List<string> { "Dave", "Eve" } },
n
...Deferred Execution
LINQ queries don't execute when defined — they execute when you iterate. Modifying the source after defining a query changes results. Use ToList() to take a snapshot.
Deferred Execution — When LINQ Runs
See exactly when each element is evaluated during iteration.
using System;
using System.Linq;
using System.Collections.Generic;
class Program
{
static void Main()
{
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// This does NOT execute yet — it's just a query definition
var query = numbers.Where(n =>
{
Console.WriteLine($" Evaluating: {n}");
return n > 2;
});
Console.WriteLine("Query defined — nothing executed yet!");
Console.WriteLine("\nNow iterating (execution
...Common Mistakes
- • Multiple enumeration: Iterating a deferred query twice runs the pipeline twice. Use
ToList(). - •
Count() > 0: Counts ALL items. UseAny()— stops at first match. - •
Select().Where(): Transforms all items then filters. UseWhere().Select(). - •
ToDictionarywith duplicate keys: Throws. UseToLookup()for multi-value keys.
LINQ Performance Patterns
Order matters. Filter early, sort smaller collections, project only needed fields, and limit with Take(). Use ToDictionary for O(1) lookups instead of repeated Where.
LINQ Performance — Filter Early
Optimise a 1,000-item query with proper ordering and ToDictionary.
using System;
using System.Linq;
using System.Collections.Generic;
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
public bool InStock { get; set; }
}
class Program
{
static void Main()
{
var products = Enumerable.Range(1, 1000).Select(i => new Product
{
Id = i,
Name = $"Product {i}",
Price = (decimal)(i * 1.5),
...Pro Tips
- 💡
ToLookup()materialises immediately — faster than repeatedGroupBy. - 💡
Aggregate()is LINQ's reduce — define custom reduction logic. - 💡
Chunk(100)splits into batches (.NET 6+) — perfect for batch API calls. - 💡
DistinctBy()(.NET 6+) — get one item per key without GroupBy + First.
Lesson Complete! 🎉
You've mastered advanced LINQ — the queries that separate juniors from seniors. Continue exploring the Advanced Track!
Sign up for free to track which lessons you've completed and get learning reminders.