LINQ Patterns I Use Every Week
Practical LINQ recipes that solve real problems: grouping with lookups, pivoting, window functions, and when to drop down to raw SQL.
- Author
- Randhir Jassal
- Published
- Reading time
- 7 min read
GroupBy + ToLookup — the difference matters
GroupBy is lazy. ToLookup is eager and indexed.
var byCategory = products.ToLookup(p => p.CategoryId);
var electronics = byCategory[electronicsId]; // O(1), pre-indexed
Use ToLookup when you'll do multiple lookups against the same grouping.
Joining with grouping (a LINQ specialty)
var customerOrderCounts =
from c in customers
join o in orders on c.Id equals o.CustomerId into customerOrders
select new {
Customer = c,
OrderCount = customerOrders.Count(),
TotalSpend = customerOrders.Sum(o => o.Total)
};
into creates a group — every customer appears once, with their orders aggregated. Equivalent to SQL's LEFT JOIN + GROUP BY.
Aggregate as a fold
When Sum/Min/Max/Average aren't enough, Aggregate is your fold-left.
var path = points.Aggregate(
seed: new PathBuilder(),
func: (b, p) => b.LineTo(p)
).Build();
Window-function-style running totals
LINQ has no built-in window functions, but you can build one cheaply.
var withRunningTotal = transactions
.OrderBy(t => t.Date)
.Select((t, i) => new { t.Date, t.Amount, Running = 0m })
.ToList();
decimal running = 0;
for (int i = 0; i < withRunningTotal.Count; i++) {
running += withRunningTotal[i].Amount;
withRunningTotal[i] = withRunningTotal[i] with { Running = running };
}
For anything heavier (rank, lead/lag), drop to SQL or dbContext.Database.SqlQuery<T>().
DistinctBy is the underrated win
var firstPerCustomer = orders
.OrderByDescending(o => o.PlacedAt)
.DistinctBy(o => o.CustomerId);
Latest-per-group in one line. Available since .NET 6.
Deferred execution traps
LINQ-to-Objects is lazy. This iterates the source twice and runs Heavy() twice as well:
var q = items.Where(x => Heavy(x));
var a = q.Count();
var b = q.ToList();
Call .ToList() once and reuse. Or .ToArray() if you won't add/remove.
When LINQ is the wrong tool
- Sorting > 100K elements with a custom comparer — go raw
Array.Sort - Complex aggregations the SQL engine can handle better — push to the DB
- Operations on Span/Memory — write the loop
LINQ is for readability. When readability is no longer the bottleneck, change tools.
Get the next issue
A short, curated email with the newest posts and questions.