Skip to content
Theory · Lambdas and LINQ · lesson 4 of 10

Closures

Picture a recipe card that says not "two spoons of sugar" but "sugar from the blue jar on the shelf". The cook goes to the jar when they start cooking and takes what is in it at that minute. If someone tops up the jar after you hand over the card, the cook finds that sugar too.

A lambda can refer to things the same way: not to a value, but to a variable from the surrounding code. Let's see what follows from that:

C#
int limit = 3;
Func<int, bool> isBig = n => n > limit;
Console.WriteLine(isBig(5)); // True
limit = 10;
Console.WriteLine(isBig(5)); // False

A lambda reads a variable

Let's go through the program line by line:

  1. int limit = 3; declares an ordinary variable.
  2. The lambda n => n > limit uses limit, although it is not its parameter. The lambda is written in the same code that declares limit, so it sees that variable, as the lines around it do.
  3. isBig(5) compares 5 > 3 and prints True.
  4. limit = 10; changes the variable after the lambda was created.
  5. The second call reads limit again and compares 5 > 10: it prints False.

A lambda that uses a variable from the surrounding code is called a closure. A closure holds not the variable's value at the moment it was created, but the variable itself, the way the card holds not sugar but directions to the jar. That is why it sees every change.

Interviewers like to ask this: what does a closure capture, the value or the variable? The answer: the variable, so a call sees its current value.

A lambda changes a variable

The link works the other way too: a closure can write a new value into the variable, and all the surrounding code sees it.

C#
int total = 0;
Action<int> add = x => total += x;
add(5);
add(3);
Console.WriteLine(total); // 8
Reset();
Console.WriteLine(total); // 0

void Reset() => total = 0;

Each call of add adds a number to the same total, so after two calls it holds 8.

The method Reset also changes total, although it does not get it through a parameter. In the methods theme, a method talked to the calling code through parameters and a result. But a method declared among top-level statements is formally a local function, and it sees the variables around it the same way a lambda does. That is a closure too.

A variable outlives its method

A closure holds the variable for as long as the closure itself lives, even after the method that declared the variable has finished:

C#
Func<int> a = MakeCounter();
Func<int> b = MakeCounter();
Console.WriteLine(a()); // 1
Console.WriteLine(a()); // 2
Console.WriteLine(b()); // 1

Func<int> MakeCounter()
{
    int n = 0;
    return () => ++n;
}

Let's see what happens here:

  1. Each call of MakeCounter creates a new variable n set to 0 and returns a lambda that increments it.
  2. MakeCounter finishes, but its n does not disappear: the returned lambda holds it.
  3. a() increments its n twice and returns 1, then 2.
  4. b() got a different n from the second call of MakeCounter, so it starts at 1.

Here the jar analogy ends. A jar stands on the shelf without any card, but the variable n lives as long as a lambda holds it. Nothing outside can reach it: the count changes only through a call of a().

Common mistake

Lambdas were created in a for loop in the hope that each one would remember its own number:

C#
List<Func<int>> getters = [];
for (int i = 0; i < 3; i++)
{
    getters.Add(() => i); // ✗
}
foreach (Func<int> g in getters)
{
    Console.WriteLine(g()); // 3
                            // 3
                            // 3
}

The program compiles, but it prints 3 three times, not 0, 1 and 2. A for loop has one variable i for all its passes. All three lambdas hold that variable, not its values. When they are called, the loop is over. The name i is gone past the loop, but the lambdas hold the variable itself, and it holds 3.

The fix is a variable of its own for each pass. Inside the loop, copy i: int copy = i;, and use copy in the lambda. Then the program prints 0, 1 and 2.

A foreach loop makes such a copy itself: since C# 5, each of its passes has a variable of its own. So lambdas from a foreach over the array [0, 1, 2] return 0, 1 and 2. Interviewers like to ask about closures in a loop as well.

Key points

  • A closure is a lambda or a local function that uses a variable from the surrounding code.
  • A closure captures the variable itself, not its value: it sees every change and can change the variable too.
  • A captured variable lives as long as the lambda does, even after the method that declared it has returned.
  • A for loop has one variable for all its passes, while foreach has a fresh one for each pass.