Series · 4 articles

LINQ, from the profiler to the compiler

Four mistakes found in production, the benchmarks, and what the compiler really generates

LINQ is declarative, but execution is imperative: the same syntax hides radically different costs depending on the data structure underneath. The series starts from a real audit on a fleet dispatcher with a latency budget under 100ms, puts numbers on it with BenchmarkDotNet, and goes down to the state machine the compiler generates.

4
Published articles
60
Minutes of reading
Intermedio
Level
.NETLINQPerformanceBenchmarkDotNetOpenTelemetry

The same code, two orders of magnitude apart

.Contains() on a List is O(n). .Contains() on a HashSet is O(1). The code you write is identical. The profiler tells two different stories.

That is the thread running through the whole series: LINQ is declarative, but execution is imperative, and the same syntax hides radically different costs depending on the data structure underneath. The starting point is a real audit, on a dispatcher for a commercial vehicle fleet that keeps everything in memory to stay under 100ms of latency: four patterns found in production, and the most expensive one was doing 1,400,000 comparisons where 2000 would have been enough.

From there the series goes down: the numbers measured with BenchmarkDotNet instead of estimated, the state machine the compiler generates behind a Where(), and finally how to make execution visible with tracing instead of having to read it off the code.

What you will learn

  • Recognize the four patterns that turn linear operations into quadratic ones
  • Measure with BenchmarkDotNet instead of guessing where the cost sits
  • Understand what the compiler generates behind a Where(), and why it changes the bill
  • Make execution visible with tracing, instead of reading it off the code

Articles in the series

  1. 01
    4 LINQ Mistakes I Found in Production (And How They Cost 1000x) 17 min

    Four common LINQ patterns that turn linear operations into quadratic ones. Analysis of real cases from a commercial fleet dispatch service on .NET 8.

  2. 02
    What Does LINQ Actually Cost? Real Benchmarks on .NET 8 14 min

    Real benchmarks with BenchmarkDotNet on .NET 8: List vs HashSet, repeated scans vs GroupBy, LINQ allocations vs foreach, and the hidden cost of AsParallel on small collections.

  3. 03
    What the Compiler Generates When You Write a Where() — State Machines, Iterators, and IL 14 min

    Behind every .Where() lives a compiler-generated state machine. In .NET 8 Enumerable.Where uses specialized iterators, and a cast turns them off.

  4. 04
    Seeing LINQ in Action: Tracing with OpenTelemetry and Grafana Tempo 15 min

    Make LINQ pipeline execution visible with OpenTelemetry and Grafana Tempo. Extension methods for tracing stages, multiple enumeration, and explosive nesting on a live dashboard.