Archives

MY APPROACH

The right solution, not the biggest solution.

You don't need to know whether your problem needs an API, an automation workflow, a dashboard or a custom application.

Tell me the problem. I'll help figure out a practical way to solve it.

Closure Explained with a Counter: The Interview Question Everyone Fails

PHP

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.

Leave a comment

RDA
Rashid Anwar
Independent Developer

I’m Rashid, an independent developer focused on practical software solutions for businesses.

HAVE A PROJECT IN MIND?

Let's talk about the problem you're trying to solve.

Share what you need. We can discuss the requirements and a practical next step.

Start a Conversation