It will create a list in a single pass due to LINQ transferring data.
For example, take this:
var query = list.Where(p => p.Number > n);
This in itself does not look at any of the elements of the list. Instead, it remembers the list you are looking at, and when you start iterating over query , each time you request the next item, it will check the list items in turn until it finds a match, and then stop. For instance:
using (var iterator = query.GetEnumerator()) { iterator.MoveNext();
Each of the operations works like this - by the time you get:
var query = list.Where(...) .Select(...) .Where(...);
... when you request the first element inside the query , it will cling back (so the last Where will request the result of Select , which will request the result of the first Where , which asks for the list) and keep going until you get the result. Then, when you request the next item, it will request a Select result for the next item, etc.
ToList creates a List<T> from all the elements in its source, right away - it is impatient in this sense (and not other operators here that are lazy). But the original list will still be repeated only once.
For more information on how LINQ to Objects works, including an example implementation, you can read my Edulinq blog series .
source share