Read the syntax
Most LINQ sequence operators build a query rather than executing it immediately. Enumeration, including foreach or ToList, evaluates the query.
A deferred query can observe later changes to its source. ToList or ToArray creates a snapshot at that moment; repeat enumeration of an unsnapshotted query can repeat work.
Syntax
var query = collection.Where(predicate); // not executed yetExamples
Queries Are Not Executed Immediately
Defining a query does not run it - only enumerating it does.
List<int> numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(n =>
{
Console.WriteLine($"Checking {n}");
return n > 1;
});
Console.WriteLine("Query defined, but not run yet");
var result = query.ToList(); // THIS is when "Checking..." actually prints- Defining a query does not run it - only enumerating it does.
- Most LINQ sequence operators build a query rather than executing it immediately. Enumeration, including foreach or ToList, evaluates the query.
- Creating query prints nothing from its predicate. ToList later enumerates it, prints Checking for each source value and retains those greater than 1.
A Deferred Query Re-Reads the Source
Because execution is deferred, changes to the source between enumerations are picked up.
List<int> numbers = new List<int> { 1, 2, 3 };
var evens = numbers.Where(n => n % 2 == 0);
numbers.Add(4);
numbers.Add(6);
Console.WriteLine(string.Join(", ", evens)); // 2, 4, 6 - includes the new items!- Because execution is deferred, changes to the source between enumerations are picked up.
- Most LINQ sequence operators build a query rather than executing it immediately. Enumeration, including foreach or ToList, evaluates the query.
- The Where query is still deferred when 4 and 6 are added. Join enumerates afterward, so it sees 2, 4 and 6.
Forcing Immediate Execution
Calling ToList(), ToArray(), or Count() forces the query to run right away and captures a snapshot.
List<int> numbers = new List<int> { 1, 2, 3 };
List<int> evensSnapshot = numbers.Where(n => n % 2 == 0).ToList(); // runs NOW
numbers.Add(4);
Console.WriteLine(string.Join(", ", evensSnapshot)); // 2 - unaffected by the later Add- Calling ToList(), ToArray(), or Count() forces the query to run right away and captures a snapshot.
- Most LINQ sequence operators build a query rather than executing it immediately. Enumeration, including foreach or ToList, evaluates the query.
- ToList evaluates the query before 4 is added, capturing only 2. Printing the snapshot later still shows 2.
Best practices
- Call .ToList() or .ToArray() when you need a stable snapshot of results that will not change if the underlying source is modified later
- Be cautious about enumerating the same deferred query multiple times when the source (or the query itself) is expensive to compute - each enumeration re-runs the whole pipeline
- Remember methods like Count(), Sum(), First(), and ToList() all trigger immediate execution of everything before them in the chain
- Watch for 'multiple enumeration' bugs where a deferred query is enumerated more than once unintentionally (e.g. once in an if check, again in a loop) - materialize it first with .ToList() to avoid redundant work
Runtime & versions
.NET runtime. Feature availability depends on the language and runtime version used by your project; consult the official reference below.
C# language reference