LINQ Performance in .NET 9 / 10 — Where the JIT Helps You and Where It Doesn't
The runtime quietly made LINQ a lot faster. The patterns that still cost you are the ones the JIT can't fix on your behalf.
There's a perennial blog post that goes "LINQ is slow, use for loops." It hasn't aged well. .NET 9 (and 10) made huge swathes of LINQ effectively free. The patterns that still hurt are the ones the JIT can't see through.
Here's the cheat sheet I keep open:
The runtime fixed these:
arr.Count()on a known-length collection — folds toarr.Length.Take(0),Skip(huge),Distinct().Count()short-circuit.Sum,Average,Min,Maxoverint[]anddouble[]are vectorised (SIMD).Select(...).First()doesn't enumerate everything anymore.
The runtime can't fix these. They're on you:
- Multiple enumeration.
var q = items.Where(...);and then iteratingqtwice means the predicate runs twice. Call.ToList()once or useforeach. - Closures capturing locals.
items.Where(x => x.Id == id)allocates a closure forid. In a hot path, hoist it or use astaticlambda withstate. ToList()in a hot loop. Allocates a new list per iteration. If you only need to iterate, drop theToList(). If you need a buffer, useArrayPool<T>.OrderBy()in a hot path. Quicksort allocates and isn't free. If the list is small and bounded, a manual sort orSpan<T>.Sort()wins by an order of magnitude.
Quick benchmark on a 10k-int array, summed:
| Pattern | Mean | Allocated |
|---|---|---|
arr.Sum() (.NET 9, SIMD) |
1.8 µs | 0 B |
Hand-written for loop |
1.7 µs | 0 B |
arr.Where(x => x > 0).Sum() |
24 µs | 40 B |
Same, with static lambda |
22 µs | 0 B |
.Sum() is now within rounding error of the manual loop. The cost shows up the moment you add a delegate. That's the actual rule: pay for Where/Select delegates when you need readability, save them for hot loops where you can prove the cost.
The only "always" advice: stop turning IEnumerable into List when you don't need to. Half the LINQ bills I've seen at scale were .ToList().Where(...) instead of just .Where(...). Removing the extra allocation is free and the next person who reads the code won't have to wonder why it was there.