Skip to content
Search lessons, topics, tests…
Esc

    ↑ ↓ moveEnter openEsc close
    Cheat sheets: all cheat sheets

    LINQ to Objects Execution and Enumeration Checklist for Interviews

    Identify when LINQ to Objects queries execute. • Predict repeated enumeration and repeated work. • Choose when to capture a materialized snapshot.

    Learning outcome

    By the end of this lesson, you should be able to explain when a LINQ to Objects query is only a recipe and when it actually runs, predict the effect of enumerating the same sequence more than once, and choose when to force a snapshot with materialization. Interviewers often want to know whether you understand that a query like Where(...) is usually deferred, while operators such as ToList() and ToArray() immediately enumerate the source. They also want you to notice hidden costs: repeated database access is not the LINQ to Objects topic, but repeated work over an in-memory sequence absolutely is.

    Intuition

    Think of a LINQ query as a description of a walk through data, not the walk itself. The query object can be created cheaply, passed around, and stored. Deferred operators postpone traversal until a consumer requests results. A foreach enumerates the sequence; scalar operators such as Count() and First() evaluate a result, but can use collection metadata or indexing instead of enumerating every element. This matters because the source sequence may be cheap, expensive, changing, or even one-shot. If you enumerate twice, you often repeat the whole pipeline twice.

    A useful interview rule is:

    • deferred operators describe work
    • terminal operators trigger work
    • materialization captures a snapshot at a moment in time

    Deep dive

    LINQ to Objects is built around IEnumerable<T>. Many query operators, including Where and Select, are deferred and stream items. Others, such as OrderBy and GroupBy, defer execution but buffer the source before yielding results. That means the source is revisited only as needed, and intermediate operators may run for each enumeration. If the source is a generator with side effects, those side effects also repeat.

    Consider the difference between these behaviors:

    • Where, Select, Skip, and Take usually defer execution
    • OrderBy must inspect the whole sequence before yielding the first result
    • ToList, ToArray, and ToDictionary force immediate execution and store results
    • Count, Any, First, and Last evaluate immediately; how much they enumerate depends on the operator and source, and optimized collection paths can avoid enumeration

    A snapshot is important when the source can change between enumerations. If you keep a query around and the underlying list changes, later enumeration sees the new state. If you need stable results, materialize once.

    Failure modes

    Common interview traps include:

    1. Assuming query creation executes immediately. It does not for most standard operators.
    2. Assuming enumerating a query twice is free. It repeats the pipeline.
    3. Forgetting that ToList() changes a lazy sequence into a snapshot.
    4. Treating Count() on a query as a property access. It may enumerate the whole sequence.
    5. Ignoring side effects in selectors or predicates. Those can happen multiple times if the sequence is re-enumerated.
    6. Confusing deferred execution with streaming: OrderBy and GroupBy are deferred but buffer data, whereas ToLookup, ToList and ToArray evaluate immediately.

    Interview drill

    Try answering these in one sentence each:

    • What happens when you assign var q = numbers.Where(...);?
    • Why might q.Count() be more expensive than expected?
    • What changes if you call ToList() before iterating?
    • What happens if the source list is modified after query creation but before enumeration?
    • How can you avoid repeating expensive computation across multiple passes?

    A strong answer mentions deferred execution, repeated enumeration, and snapshotting with materialization.

    Revision checklist

    Use this checklist before an interview:

    • Can I explain deferred vs immediate execution clearly?
    • Do I know which operators are lazy and which force full evaluation?
    • Can I describe what happens on first and second enumeration?
    • Can I explain why materializing a query creates a snapshot?
    • Can I warn about side effects inside predicates and selectors?
    • Can I recognize when Count(), Any(), or First() still enumerate?

    Production code

    The example below shows deferred execution, repeated enumeration, and snapshot behavior using a deterministic in-memory sequence. The source is a list created before the query. The loops show the results of repeated enumeration and the effect of adding a source item after materialization.

    Code walkthrough

    The output ordering is the whole lesson:

    • Source created is printed first because the list is built immediately. Defining the Where/Select query does not evaluate its predicates or projections; those run when the result is enumerated.
    • The first foreach walks the source and produces even numbers multiplied by ten.
    • The second foreach runs the whole pipeline again, proving repeated enumeration.
    • ToList() materializes the query into snapshot, so later changes to source do not affect the stored list.
    • Enumerating query after adding 6 to the source sees the new element, because the query is not a frozen result.

    A useful interview phrasing is: “A LINQ query is a reusable plan, not a cached result, unless I materialize it.” That single sentence captures the main behavior.

    Output: Deferred execution, repeated enumeration, and snapshot with LINQ to Objects

    Source created
    Query created
    First enumeration:
    item=20
    item=40
    Second enumeration:
    item=20
    item=40
    Snapshot count=2
    After snapshot, mutate source and enumerate snapshot again:
    snapshot-item=20
    snapshot-item=40
    Enumerate query after mutation:
    item=20
    item=40
    item=60

    Snapshot scope

    Materialization stores the selected entries, not deep copies of referenced objects. A later change to a shared mutable object can still be visible in both collections.

    Executable code examples

    Deferred execution, repeated enumeration, and snapshot with LINQ to Objects

    Program.cs

    C#Runs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    var source = CreateNumbers();
    var query = source.Where(n => n % 2 == 0).Select(n => n * 10);
    
    Console.WriteLine("Query created");
    
    Console.WriteLine("First enumeration:");
    foreach (var item in query)
    {
        Console.WriteLine($"item={item}");
    }
    
    Console.WriteLine("Second enumeration:");
    foreach (var item in query)
    {
        Console.WriteLine($"item={item}");
    }
    
    var snapshot = query.ToList();
    Console.WriteLine($"Snapshot count={snapshot.Count}");
    
    Console.WriteLine("After snapshot, mutate source and enumerate snapshot again:");
    source.Add(6);
    foreach (var item in snapshot)
    {
        Console.WriteLine($"snapshot-item={item}");
    }
    
    Console.WriteLine("Enumerate query after mutation:");
    foreach (var item in query)
    {
        Console.WriteLine($"item={item}");
    }
    
    static List<int> CreateNumbers()
    {
        var numbers = new List<int> { 1, 2, 3, 4 };
        Console.WriteLine("Source created");
        return numbers;
    }

    TechInterviewNotes Editorial Team · Updated 25 Oct 2026 ·