What are 'closures' in .NET?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Closures are the mechanism that lets a lambda or local function keep using variables from the surrounding scope after that scope would normally be gone. In day-to-day C# code, this matters whenever you pass behavior around with LINQ, callbacks, asynchronous work, or factory methods.
What a Closure Really Captures
In C#, a closure captures variables, not just the values those variables held at one instant. The compiler rewrites the code so the captured variables live inside a generated helper object, sometimes called a display class. The lambda then reads and writes fields on that helper object.
That behavior explains why closures are useful and why they sometimes surprise people.
Here is a simple example:
Even though CreateCounter has already returned, the lambda still has access to count. That preserved state is the closure.
Why Closures Are Useful
Closures are a lightweight way to attach state to behavior without creating a dedicated class every time. They are especially convenient for small pieces of business logic.
You will see them often in LINQ:
The predicate closes over minimum. If minimum changes before enumeration happens, the query sees the new value because it captured the variable itself:
That interaction between closures and deferred execution is important. A LINQ query may not evaluate when you write it, only when you enumerate it.
The Classic Loop-Capture Bug
The most famous closure mistake is capturing a loop variable and expecting each lambda to get a different copy.
This prints 3 three times, not 0, 1, and 2. All lambdas captured the same i, and by the time they run the loop has finished.
The fix is to create a new local variable inside the loop:
Now each lambda closes over a different variable.
Closures with Async Code
Closures are also common in asynchronous code because lambdas are frequently passed into tasks, timers, and event handlers. The same rules apply: if the variable changes before the callback runs, the callback sees the changed variable.
This is often desirable, but it becomes risky if multiple callbacks mutate shared captured state concurrently. In that case, you may need locking or a different design.
When a Closure Is Better Than a Class
Use a closure when the state is small, short-lived, and tightly coupled to one piece of behavior. If the state grows, needs multiple methods, or deserves a clear name in the domain model, a dedicated type is usually easier to maintain.
Closures are a tool for reducing noise, not for hiding important structure. If future readers must reverse-engineer what several captured variables mean, the closure has already become too clever.
Common Pitfalls
- Forgetting that closures capture variables by reference-like behavior, not a frozen snapshot.
- Combining closures with deferred LINQ execution and then wondering why later variable changes affect the query.
- Capturing loop variables without creating a per-iteration copy.
- Holding large objects in captured variables longer than intended, which can extend their lifetime and memory use.
- Mutating captured state from multiple asynchronous callbacks without synchronization.
Summary
- A closure lets a lambda or local function keep using surrounding variables after the outer method returns.
- In C#, the compiler implements this by moving captured variables into a generated helper object.
- Closures are useful for counters, callbacks, LINQ filters, and small stateful functions.
- The most common bug is capturing the loop variable instead of a per-iteration copy.
- If the captured state becomes large or central to the design, a named type is often clearer.

