Deferred execution
Deferred execution is one of the key characteristics of LINQLINQ: query syntax and method syntaxOne SQL-like way to query many kinds of data, written in two equivalent forms.Opens in a new tab. Queries are not executed where they are defined. Execution is deferred until the query variable is actually enumerated, for example in a foreach loop. It also happens when you call a conversion operator like ToList(), ToArray(), First() or Count().
Where it matters
Deferred execution lets you build complex queries dynamically by chaining operations. It also means the query works on the most current data at the time it runs.
It can be a pitfall if it isn’t well understood. Enumerating a LINQ query repeatedly makes it run again each time, and that gets expensive when the query involves significant computation or database access. To avoid it, when you need the results more than once, materialize the query into a collection (for example by calling .ToList()) right after defining it.
Which operators are which
| Deferred (lazy) | Immediate (eager) |
|---|---|
Where |
ToList(), ToArray() |
Select |
Count() |
OrderBy |
First(), FirstOrDefault() |
Take, Skip |
Sum(), Max(), Min(), Average() |
GroupBy |
Any(), All() |
IEnumerable<T>, it’s deferred. If it returns a concrete value or collection type (an int, a List<T>, a single item), it has already run.
The proof
Nothing runs at the Where line. The lambda only executes when the foreach starts pulling items, and by then it looks at the current state of numbers, which now includes 10. ToList() forces immediate execution: the query runs right then and locks in the results, so a later change to numbers doesn’t affect it. See ToList and what it returnsToList and what it returnsToList runs the query and returns a List, which offers more than IEnumerable.Opens in a new tab.
List<int> numbers = new List<int> { 1, 2, 3 }; var query = numbers.Where(n => n > 1); // NOT executed yet, just stored numbers.Add(10); // the list changes before enumeration foreach (var n in query) Console.WriteLine(n); // 2, 3, 10 // Now with ToList(), which runs right now and takes a snapshot:var snapshot = numbers.Where(n => n > 1).ToList(); numbers.Add(20); foreach (var n in snapshot) Console.WriteLine(n); // 2, 3, 10 (no 20)Running it twice
Enumerating the same deferred query twice runs it twice. If the source is expensive, like a database call, that’s wasteful. Call .ToList() once and reuse the result.
var query = numbers.Where(n => n > 1); Console.WriteLine(query.Count()); // runs the filterConsole.WriteLine(query.Count()); // runs the filter AGAINChains stay lazy
In a chain like .Where(...).Select(...), nothing has run until the end. Each item then flows through Where and then Select one at a time as the foreach pulls it. It is not “filter everything, then transform everything” in separate passes.
Sources
- Write LINQ queries, C#, Microsoft Learn · accessed May 16, 2025