The LINQ Query That Never Runs (Until You Force It)
You write a LINQ query. It compiles. It looks correct. But something is off — it executes at the weirdest times, or worse, executes multiple times when you expected once.
Welcome to deferred execution — LINQ's most misunderstood feature.
The Illusion of Immediate
Most developers assume this runs immediately:
var filtered = products.Where(p => p.Price > 100);
It doesn't. Not a single item is touched. The filtered variable holds a recipe, not a meal. The actual filtering happens only when you iterate — foreach, ToList(), Count(), First().
Here's where it gets interesting: LINQ remembers the source, not the snapshot.
var numbers = new List<int> { 1, 2, 3 };
var doubled = numbers.Select(n => n * 2);
numbers.Add(4); // Modify the source
foreach (var n in doubled)
Console.WriteLine(n); // Prints 2, 4, 6, 8 — includes the new element!
The query re-evaluates against the current state of numbers. Every. Single. Time.
The Multiple Execution Trap
This pattern destroys performance in real codebases:
var expensive = GetProducts()
.Where(p => p.Category == "Electronics")
.Select(p => new { p.Name, p.Price });
// First enumeration — database hit
var count = expensive.Count();
// Second enumeration — ANOTHER database hit
var first = expensive.First();
// Third enumeration — you get the idea
foreach (var item in expensive) { }
Three database calls. Same query. Because expensive is a recipe, not results.
Force Immediate Execution
The fix is simple — materialize when you need stable data:
var products = GetProducts()
.Where(p => p.Category == "Electronics")
.ToList(); // Execute NOW, store results
var count = products.Count; // Just counts the list
var first = products.First(); // Just reads from memory
ToList(), ToArray(), ToDictionary() — these force execution and store results.
Fun Fact
The term "deferred execution" comes from functional programming's "lazy evaluation" — a concept dating back to 1976 with SASL language. LINQ brought this academic concept into mainstream C# development in 2007, and developers have been confused by it ever since.
When Deferred Execution Shines
It's not a bug — it's a feature. Consider:
IEnumerable<Product> BuildQuery(bool includeInactive)
{
var query = products.Where(p => p.Price > 0);
if (!includeInactive)
query = query.Where(p => p.IsActive);
return query.OrderBy(p => p.Name);
}
You can compose queries conditionally before execution. The database sees one optimized query, not three separate ones.
The Rule
- Building a query? Let it stay deferred — compose freely.
- Passing results around? Materialize with
ToList(). - Multiple enumerations? Materialize first.
- Single enumeration? Deferred is fine.
Next time, we'll look at how IQueryable takes deferred execution even further — translating C# into SQL you never wrote. Hope to see you!
Top comments (0)