Published on 22-05-2015

Deferred and Immediate Execution in LINQ

LINQ queries can execute in two different ways: deferred and immediate. This post covers how both of these execution models work.

With deferred execution, the resulting sequence of a LINQ query is not generated until it is required. Take this small piece of code as an example:

int[] numbers = { 1, 2, 3, 4, 5 };
            
var result = numbers.Where(n => n >= 2 && n <= 4);
            
Console.WriteLine(result.Max()); // <- query executes at this point
                                           
// Output:
// 4

This query does not actually execute until Max() is called, and a final result is required. You will be able to see this for yourself, if you step through the code in a debugger. When the debugger reaches Max(), notice that the execution pointer jumps back to the query’s Where() operator in order to generate a final result.

Deferred execution makes LINQ more efficient, as it prevents full execution of chained operations for each item it has to process. The below code example demonstrates the concept of chained operations in LINQ:

string[] words = { "one", "two", "three" };
            
var result = words.Select((w, i) => new { Index = i, Value = w }).Where(w => w.Value.Length == 3).ToList();

Debug.WriteLine("Prints index for words that have a string length of 3:");
foreach(var word in result)
    Debug.WriteLine (word.Index.ToString());

// Output:
// Prints index for words that have a string length of 3:
// 0
// 1

Instead of LINQ having to first iterate over all three chains (first Select(), then Where() and lastly ToList()) the result is not generated until it meets ToList(). However, as we will see in a second it is not only chained operations, which makes deferred execution powerful.

Adding items to an existing query is another benefit of deferred execution. This example shows the concept:

List vegetables = new List { "Carrot", "Selleri" };

var result = from v in vegetables select v;

Debug.WriteLine("Elements in vegetables array (before add): " + result.Count());
            
vegetables.Add("Broccoli");
            
Debug.WriteLine("Elements in vegetables array (after add): " + result.Count());

// Output:
// Elements in vegetables array (before add): 2
// Elements in vegetables array (after add): 3

"Broccoli" is added to the existing query, but the query is not executed until Count() is reached.

Deferred execution makes it useful to combine or extend queries. Have a look at this example, which creates a base query and then extends it into two new separate queries:

int[] numbers = { 1, 5, 10, 18, 23};

var baseQuery = from n in numbers select n;

var oddQuery = from b in baseQuery where b % 2 == 1 select b;

Debug.WriteLine("Sum of odd numbers: " + oddQuery.Sum()); // <- query executes at this point
                                                                            
var evenQuery = from b in baseQuery where b % 2 == 0 select b;
            
Debug.WriteLine("Sum of even numbers: " + evenQuery.Sum()); // <- query executes at this point

// Output:
// Sum of odd numbers: 29
// Sum of even numbers: 28

Notice how query execution is not performed until Sum() is reached.

When LINQ queries execute and evaluate promptly, you have what is known as immediate execution (which as you may have guessed by now is the exact contrast to deferred execution). In fact, all LINQ queries, which return singleton values, execute immediately; these include ToList(), Average() and Sum(). Conversely, you have operators like Where, Select and Take for deferred execution. Thus, if you want to force immediate execution, one option is to finish the query calling chain with ToList().

Here is a complete list of LINQ operators (in alphabetical order) classified to how they execute:

Immediate execution:
Aggregate, All, Any, Average, Contains, Count, ElementAt, ElementAtOrDefault, Empty, First, FirstOrDefault, Last, LastOrDefault, Max, LongCount, Min, SelectMany, SequenceEqual, Single, SingleOrDefault, Sum, ToArray, ToDictionary, ToList, ToLookup.
Deferred execution
AsEnumerable, Cast, Concat, DefaultIfEmpty, Distinct, Except, GroupBy, GroupJoin, Intersect, Join, OfType, OrderBy, OrderByDescending, Range, Repeat, Reverse, Select, SelectMany, Skip, SkipWhile, Take, TakeWhile, ThenBy, ThenByDescending, Union, Where.

On a final note, you find the concept of deferred and immediate execution in linq providers such as LINQ to Objects, LINQ to SQL and Linq to Entities.

As you have seen in this tutorial, it is important to have a good understanding of deferred and immediate execution, to write better performing LINQ queries. On top of this, it might save you from a bit of confusion when you debug future code.