Skip to Content
Course content

35: Deferred Execution in LINQ

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent a lot of time reviewing PRs from junior devs, and there is one specific "aha!" moment that almost always happens around the same time: the realization that a LINQ query isn't actually a result, but a set of instructions. When you first start using LINQ, it feels like you're calling a function that returns a filtered list. It isn't.

The Idea That var query = ... Actually Runs the Query

Here is the trap. You might write some code like this, expecting the highValueOrders variable to hold a snapshot of the data at the moment you defined it:

var orders = new List<Order> { 
    new Order { Id = 1, Total = 100 }, 
    new Order { Id = 2, Total = 50 } 
};

// You think the filtering happens RIGHT HERE
var highValueOrders = orders.Where(o => o.Total > 80);

// Now you add a new order to the source list
orders.Add(new Order { Id = 3, Total = 200 });

foreach (var order in highValueOrders)
{
    Console.WriteLine(order.Id);
}

If you think the query executed on line 6, you'd expect the output to be just 1. But when you actually run this, the output is 1 AND 3. This confuses people because it feels like the variable highValueOrders "magically" updated itself. It didn't. It never held any orders to begin with.

Thinking of LINQ as a Blueprint, Not a Result

What actually happened is called Deferred Execution. When you call .Where(), .Select(), or .OrderBy(), C# doesn't go hunting through your list. Instead, it creates an IEnumerable object—essentially a blueprint or a "to-do list" that says: "Whenever someone eventually asks me for the items, I will go to the original list and filter for totals over 80."

The query only actually executes when you iterate over it. In the example above, the execution happened inside the foreach loop. Because the source list had changed by the time the loop started, the query saw the updated data.

I'll be honest: this is a double-edged sword. On one hand, it's incredibly efficient. You can chain five different filters together, and LINQ will process each item through the whole chain one by one, rather than creating five intermediate lists in memory. On the other hand, if you accidentally put a heavy database call inside a deferred query and then iterate over that query three times in your code, you've just hit your database three times.

Forcing the Issue with Immediate Execution

If you actually want a snapshot of the data right now, you need to trigger Immediate Execution. You do this by calling a method that requires the actual values to produce a result. The most common ones are .ToList(), .ToArray(), .Count(), or .First().

Look at the difference here:

// This executes IMMEDIATELY. 
// The filter runs now, and the results are stored in a new list.
var highValueOrders = orders.Where(o => o.Total > 80).ToList();

orders.Add(new Order { Id = 3, Total = 200 });

// Now, this will only print '1' because the snapshot was taken before order 3 existed.
foreach (var order in highValueOrders)
{
    Console.WriteLine(order.Id);
}

My rule of thumb? If you're passing a query around to different methods or if the underlying data is going to change, call .ToList() early to avoid "ghost" data changes and unexpected performance hits.




📋 Practical Task

The Dynamic Inventory Filter Glitch

You are building a warehouse management system. You have a list of Product objects. Your task is to demonstrate that you understand the difference between deferred and immediate execution by fixing a bug in the following scenario.

The Goal: Create a "Snapshot" of out-of-stock products. Even if products are restocked (added back to the inventory) after the snapshot is taken, the snapshot should still show the products that were out of stock at the moment the snapshot was created.

public class Product {
    public string Name { get; set; }
    public int StockQuantity { get; set; }
}

public class Program {
    public static void Main() {
        var inventory = new List<Product> {
            new Product { Name = "Laptop", StockQuantity = 0 },
            new Product { Name = "Mouse", StockQuantity = 10 },
            new Product { Name = "Keyboard", StockQuantity = 0 }
        };

        // TODO: Create a variable called 'outOfStockSnapshot' 
        // that captures all products with StockQuantity == 0.
        // Ensure this uses IMMEDIATE execution so it doesn't change later.
        var outOfStockSnapshot = // Your code here

        // Simulating a restock: The Laptop is now back in stock
        inventory[0].StockQuantity = 5;

        // This should print "Laptop" and "Keyboard" 
        // because they were out of stock when the snapshot was taken.
        foreach (var p in outOfStockSnapshot) {
            Console.WriteLine(p.Name);
        }
    }
}

Requirements:

  • Modify the outOfStockSnapshot assignment to ensure the results are materialized immediately.
  • The final output must be "Laptop" and "Keyboard", despite the Laptop's stock being updated to 5.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.