
Understanding LINQ Deferred, Immediate, Streaming and Non-Streaming Executions
Author - Abdul Rahman (Bhai)
LINQ
26 Articles
Table of Contents
What we gonna do?
In this article, let's learn about how Execution and Operators works in LINQ in .NET.
Note: If you have not done so already, I recommend you read the article on Using LINQ For Each to iterate Collections.
LINQ to Objects has two ways of running its standard query operator methods: right away or later on. When using the later option, it can either stream the data or not. Knowing which option you're using can help you understand the results you get from your query, especially if the data is changing or if you're building a query on top of another one. This topic organizes the standard query operators by how they work.
LINQ has four Manners of Execution. They are,
- Immediate
- Deferred
- Streaming
- Non-Streaming

Understanding LINQ Execution Manners and Operators has the following advantages
- Better Performance.
- Less Iterations.
Why we gonna do?
Without understanding execution strategies, developers may write queries that execute more than necessary or produce unexpected results. Knowing when a query runs (immediate vs deferred) and how it reads data (streaming vs non-streaming) directly impacts performance.
How we gonna do?
Immediate Execution
Immediate execution in LINQ to Objects means that the data is read and the operation is done right away. Standard query operators that return a single / scalar value always work this way. You can make a query execute immediately by using either Enumerable.ToList() or Enumerable.ToArray() methods. Immediate execution helps you save time by reusing the results of your query instead of running it again. The query results are stored for later use after being retrieved once.
int[] numbers = { 1, 2, 3, 4, 5 };
int sum = numbers.Sum();
Console.WriteLine($"The sum of the numbers is {sum}.");
The sum of the numbers is 15.
//we create an array of integers called numbers with the values { 1, 2, 3, 4, 5 }.
//Then, we use the LINQ extension method Sum to calculate the sum of all the numbers in the array.
//Since Sum is an immediate execution operator, the result is calculated immediately and assigned to the sum variable.
Deferred Execution
Deferred execution in LINQ to Objects means that the query doesn't run when it's defined in the code. Instead, it runs only when you actually use the query results, for example in a foreach statement. This means that the query results can change depending on what's in the data source at the time the query is used, not when it's written. If you use the query results multiple times, you might get different results each time. Most standard query operators that return IEnumerable<T> or IOrderedEnumerable<TElement> work this way. Deferred execution lets you reuse queries, and it fetches updated data from the data source each time you iterate through the query results.
Query operators that use deferred execution can also be divided into two types: streaming and non-streaming.
int[] numbers = { 1, 2, 3, 4, 5 };
IEnumerable<int> query = numbers.Where(n => n % 2 == 0);
numbers[3] = 8;
Console.WriteLine("Query results:");
foreach (int n in query)
{
Console.WriteLine(n);
}
Query results:
2
4
//we create an array of integers called numbers with the values { 1, 2, 3, 4, 5 }.
//Then, we define a LINQ query using the Where operator to select only the even numbers from the numbers array.
//Since Where is a deferred execution operator, the query is not actually executed until we enumerate it.
//Next, we change the value of the fourth element in the numbers array to 8. This means that the original source data has changed since we defined the query.
//Finally, we enumerate the query results using a foreach loop and print them to the console using Console.WriteLine. Notice that the query results only include the even numbers in the original array (2 and 4), even though we changed one of the source elements to 8. This is because the query execution is deferred until we enumerate the query, and it uses the updated source data at that time.
Streaming Operators
Streaming operators in LINQ to Objects don't need to read all the source data before they start returning elements. Instead, they work on each source element one at a time and return the result immediately if applicable. A streaming operator keeps reading source elements until it can produce a result element. This means that sometimes it has to read more than one source element to make one result element.
int[] numbers = { 1, 2, 3, 4, 5 };
IEnumerable<int> query = numbers.Where(n => n % 2 == 0).Select(n => n * 2);
numbers[3] = 8;
Console.WriteLine("Query results:");
foreach (int n in query)
{
Console.WriteLine(n);
}
Query results:
4
8
//we create an array of integers called numbers with the values { 1, 2, 3, 4, 5 }.
//Then, we define a LINQ query using the Where and Select operators.
//The Where operator is a deferred execution streaming operator that selects only the even numbers from the numbers array.
//The Select operator is also a deferred execution streaming operator that multiplies each even number by 2.
//Next, we change the value of the fourth element in the numbers array to 8.
//This means that the original source data has changed since we defined the query.
//Finally, we enumerate the query results using a foreach loop and print them to the console using Console.WriteLine.
//Notice that the query results include the even numbers in the original array (2 and 4), but multiplied by 2 (4 and 8), even though we changed one of the source elements to 8. This is because the query execution is deferred until we enumerate the query, and it uses the updated source data at that time. Also, the query execution is streaming, so it doesn't have to read all the source data before yielding elements.
Non-Streaming Operators
Non-streaming operators in LINQ to Objects need to read all the source data before they can start returning (yielding) any result elements. Sorting and grouping are examples of non-streaming operators. When a non-streaming operator runs, it reads all the source data, puts it into a data structure, performs the operation, and then returns (yields) the resulting elements.
int[] numbers = { 1, 2, 3, 4, 5 };
IEnumerable<int> query = numbers.OrderBy(n => n).Take(3);
numbers[3] = 8;
Console.WriteLine("Query results:");
foreach (int n in query)
{
Console.WriteLine(n);
}
Query results:
1
2
3
//We create an array of integers called numbers with the values { 1, 2, 3, 4, 5 }.
//Then, we define a LINQ query using the OrderBy and Take operators.
//The OrderBy operator is a deferred execution non-streaming operator that sorts the numbers array in ascending order.
//The Take operator is also a deferred execution non-streaming operator that selects the first 3 elements of the sorted array.
//Next, we change the value of the fourth element in the numbers array to 8.
//This means that the original source data has changed since we defined the query.
//Finally, we enumerate the query results using a foreach loop and print them to the console using Console.WriteLine.
//Notice that the query results include the first 3 elements of the sorted numbers array (1, 2, and 3), even though we changed one of the source elements to 8. This is because the query execution is deferred until we enumerate the query, and it uses the updated source data at that time. Also, the query execution is non-streaming, so it has to read all the source data before yielding elements.
Classification Table
The following table classifies each standard query operator method according to its method of execution.
| Manner of Execution | Query Operators |
|---|---|
| Immediate Execution | Aggregate, All, Any, Average, Cast, Concat, Contains, Count, DefaultIfEmpty, Distinct, ElementAt, ElementAtOrDefault, Except, First, FirstOrDefault, GroupBy (with overload), GroupJoin, Intersect, Join, Last, LastOrDefault, LongCount, Max, Min, OfType, OrderBy, OrderByDescending, Range, Repeat, Reverse, Select, SelectMany, SequenceEqual, Single, SingleOrDefault, Skip, SkipWhile, Sum, Take, TakeWhile, ThenBy, ThenByDescending, ToArray, ToDictionary, ToList, Union, Where, Zip |
| Deferred Execution - Streaming | AsEnumerable, AsQueryable, Cast, Concat, DefaultIfEmpty, Distinct, GroupBy (without overload), Join, OfType, Reverse, Select, SelectMany, Take, TakeWhile, Union, Where |
| Deferred Execution - Non-Streaming | Aggregate, All, Any, Average, Contains, Count, ElementAt, ElementAtOrDefault, Except, First, FirstOrDefault, GroupBy (with overload), GroupJoin, Intersect, Last, LastOrDefault, LongCount, Max, Min, OrderBy, OrderByDescending, Single, SingleOrDefault, Skip, SkipWhile, Sum, ThenBy, ThenByDescending, ToArray, ToDictionary, ToList, Zip |
Summary
In this article we learn't how LINQ Execution and Operators work. We saw how Deferred and Immediate execution works along with Streaming and Non-Streaming operators. We learnt the advantages of deferred execution and how to use yield keyword in streaming operations with a live demo. With this I'm completing my LINQ series. Feel free to checkout LINQ learning path in LINQ Channel.