Closure Explained with a Counter: The Interview Question Everyone Fails
Here is a common interview question:
What will this function return each time it is called?
function counter(): Closure
{
$count = 0;
return function () use (&$count): int {
return ++$count;
};
}
$next = counter();
echo $next(); // ?
echo $next(); // ?
echo $next(); // ?
The output is:
123
The reason is not recursion, static variables, or a hidden global. The returned function is a closure, and it remembers the variable it captured from its surrounding scope.
What Is a Closure in PHP?
A closure is an anonymous function that can be assigned to a variable, passed as an argument, returned from another function, or stored for later use.
Unlike a regular function, a closure can also capture variables from the scope where it was created.
$taxRate = 0.18;
$calculateTax = function (float $amount) use ($taxRate): float {
return $amount * $taxRate;
};
echo $calculateTax(100); // 18
The use ($taxRate) expression imports the variable into the closure.
However, there is an important detail: variables captured with use are captured by value by default.
Why the Counter Works: Capture by Reference
Consider the counter again:
function counter(): Closure
{
$count = 0;
return function () use (&$count): int {
return ++$count;
};
}
The ampersand in use (&$count) is the entire trick.
use ($count)captures the current value.use (&$count)captures a reference to the original variable.
Because the closure captures $count by reference, each call updates the same variable:
$next = counter();
echo $next(); // 1
echo $next(); // 2
echo $next(); // 3
The local variable technically belongs to the execution scope of counter(), but the closure keeps it alive because it still references it. This behavior is often described as a closure retaining its lexical environment.
What Happens Without the Ampersand?
Now remove the reference:
function counter(): Closure
{
$count = 0;
return function () use ($count): int {
return ++$count;
};
}
$next = counter();
echo $next(); // 1
echo $next(); // 1
echo $next(); // 1
Each invocation modifies the closure’s captured copy, but that copy is not shared with the original variable. The closure does not recreate the counter on every call; instead, it repeatedly starts from the captured value of 0 for this expression.
To make state persist across calls, capture the variable by reference or use another stateful design such as a class or a static variable.
Practical Uses of Closures
Closures are not just interview puzzles. They appear throughout modern PHP applications.
- Collection transformations: Mapping, filtering, and sorting data.
- Laravel routes: Defining route actions inline.
- Query constraints: Grouping conditional query logic.
- Callbacks: Passing custom behavior into another method.
- Deferred execution: Running logic later while retaining configuration.
$minimumPrice = 500;
$products = array_filter(
$products,
fn (array $product): bool => $product['price'] >= $minimumPrice
);
Arrow functions such as fn () => ... automatically capture variables from the parent scope by value. They cannot use the traditional use clause.
Three Closure Gotchas Worth Remembering
1. By value does not mean shared state
Capturing a variable with use ($value) gives the closure its own captured value. If you need shared mutable state, use use (&$value) deliberately.
2. Captured objects behave differently from scalars
When an object is captured by value, the object handle is copied, not the entire object. Both the original scope and the closure can still refer to the same object instance.
3. Stateful closures can become difficult to test
A counter closure is useful for demonstrations and small isolated tasks. In business logic, hidden state can make behavior harder to understand, reset, serialize, or test. If the state represents an important domain concept, a small class is often clearer.
The interview lesson is simple: a closure is more than an anonymous function. It can carry the environment in which it was created. Once you understand the difference between capturing by value and capturing by reference, the counter question stops being a trick and becomes predictable PHP behavior.
Comments
No comments yet. Be the first to share your thoughts.