Closures & Arrow Functions

Reviewed & published by Brayan K

Functions in PHP are values — you can store them in variables, pass them as arguments, and return them from other functions. Master closures and the concise fn() => arrow syntax, and the whole world of array_map, array_filter and callbacks opens up.

Part of the free PHP course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ Anonymous Functions as Values

An anonymous function (also called a closure) is a function with no name. You assign it to a variable and call it through that variable with (). Because it's just a value, you can also pass it into another function — any parameter typed callable will accept it.

<?php
// A CLOSURE is a function with no name that you store in a variable
// (or pass into another function). PHP calls these "anonymous functions".

$greet = function (string $name): string {
    return "Hello, $name!";
};

// Call it through the variable, with ().
echo $greet('Ada') . "\n";        // Hello, Ada!

// Because it's just a value, you can pass it as an argument.
function runTwice(callable $fn, string $arg): void {
    echo $fn($arg) . "\n";
    echo $fn($arg) . "\n";
}
runTwice($greet, 'Sam');           // prints the greeting twice
?>

2️⃣ Capturing Scope with use()

Here's the part that surprises newcomers: a PHP closure does not automatically see variables from the code around it. You import the ones you need with use(...). By default use($x) captures a copy of the value at the moment the closure is defined. Add an ampersand — use(&$x) — to capture by reference so the closure and the outer code share one live variable.

<?php
// A closure does NOT see the outer scope automatically.
// You "capture" outer variables with the use() keyword.

$tax = 0.20;

// use ($tax) copies the value INTO the closure (by value).
$withTax = function (float $price) use ($tax): float {
    return $price + ($price * $tax);
};

echo $withTax(100) . "\n";        // 120

// Changing $tax afterwards does NOT affect the closure — it captured a copy.
$tax = 0.50;
echo $withTax(100) . "\n";        // still 120

// To capture by REFERENCE (live link), add &:  use (&$counter)
$counter = 0;
$tick = function () use (&$counter) { $counter++; };
$tick(); $tick(); $tick();
echo $counter . "\n";             // 3
?>

3️⃣ Arrow Functions

Arrow functions (PHP 7.4+) are a compact closure for a single expression: fn($x) => $x * 2. They have two superpowers over the long form: there's an implicit return, and they auto-capture outer variables by value — no use() needed. The trade-off is they're limited to one expression.

<?php
// ARROW FUNCTIONS (PHP 7.4+) are a short closure for one expression.
//   fn($x) => expression
// They AUTO-capture outer variables by value — no use() needed.

$multiplier = 3;

// Equivalent to: function ($n) use ($multiplier) { return $n * $multiplier; }
$triple = fn($n) => $n * $multiplier;

echo $triple(5) . "\n";           // 15
echo $triple(10) . "\n";          // 30

// Arrow functions shine inside array helpers.
$nums = [1, 2, 3, 4];
$doubled = array_map(fn($n) => $n * 2, $nums);
echo implode(', ', $doubled) . "\n";   // 2, 4, 6, 8
?>

4️⃣ The Array-Function Trio

Closures really earn their keep with PHP's higher-order array functions. array_filter keeps items where your closure returns true, array_map transforms each item, and array_reduce folds a list down to a single value. Arrow functions make these read almost like English.

<?php
// Closures are the engine behind PHP's array helpers.
$products = [
    ['name' => 'Pen',    'price' => 2],
    ['name' => 'Mug',    'price' => 8],
    ['name' => 'Laptop', 'price' => 950],
];

// array_filter: keep only items whose closure returns true.
$affordable = array_filter($products, fn($p) => $p['price'] < 100);

// array_map: transform each item.
$names = array_map(fn($p) => $p['name'], $affordable);

// array_reduce: fold the list into a single value.
$total = array_reduce($products, fn($carry, $p) => $carry + $p['price'], 0);

echo 'Affordable: ' . implode(', ', $names) . "\n";
echo 'Total spend: $' . $total . "\n";
?>

Now you try — fill in each ___ using the 👉 hint, then run it and check against the Output panel.

<?php
// 🎯 YOUR TURN — fill in each blank marked ___ , then run it.

$bonus = 10;

// 1) An arrow function that adds $bonus to its argument:
$addBonus = ___ ($score) => $score + $bonus;   // 👉 the arrow-function keyword

echo $addBonus(90) . "\n";        // should print: 100

// 2) Double every number with array_map and an arrow function:
$nums = [3, 6, 9];
$doubled = array_map(___ ($n) => $n * 2, $nums);   // 👉 same keyword again
echo implode(',', $doubled) . "\n";   // should print: 6,12,18

// ✅ Expected output:
//    100
//    6,12,18
?>

🧩 Reorder Challenge

These lines build a by-reference counter closure — but they're scrambled. Put them in the order that prints 2.

B → D → A → E → C

Initialise $n (B), define the closure that captures it by reference (D), call it twice (A, E) so $n becomes 2, then echo it (C). The closure must exist before you call it.

🧠 Quick Recall — Predict the Output

6 — the arrow function captured $x by value (5) when it was defined. Reassigning $x to 100 afterwards has no effect.

2. What does this print?

1,4,9 — each element is squared by the arrow function.

3. What is the final value of $total?

7 — capturing by reference (&$total) lets the closure mutate the outer variable across both calls.

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Closures

SyntaxExampleWhat It Does
function () {}$f = function () { return 1; };Anonymous function (closure)
use ($x)function () use ($x) {}Capture by value
use (&$x)function () use (&$x) {}Capture by reference
fn() =>fn($n) => $n * 2Arrow function (auto-capture)
array_maparray_map($fn, $arr)Transform each item
array_filterarray_filter($arr, $fn)Keep items where true
array_reducearray_reduce($arr, $fn, 0)Fold to one value

Mini-Challenge: Counter Factory

No code is filled in this time — just a brief and an outline. Write it yourself, run it on onecompiler.com/php or your own machine, then check your result against the expected output in the comments.

<?php
// 🎯 MINI-CHALLENGE: a counter factory.
// No code is filled in — work from the steps below, then run it.
//
// 1. Write a function makeCounter() that returns a CLOSURE.
//    Inside makeCounter, start $count = 0.
//    The returned closure must use (&$count), increment it, and return it.
// 2. Create $a = makeCounter();  and  $b = makeCounter();
// 3. Call $a() three times and $b() once, echoing each result.
//    Each counter must keep its OWN private count.
//
// ✅ Expected output:
//    1
//    2
//    3
//    1

// your code here
?>

🎉 Lesson Complete!

Practice quiz

How does a classic closure (function (...) { ... }) access a variable from the surrounding scope?

  • Automatically, like JavaScript
  • Only with the global keyword
  • You must import it with use()
  • It cannot access outer variables

Answer: You must import it with use(). A PHP closure does not inherit outer scope automatically. You explicitly import variables with use($var).

What prints? $x = 5; $f = fn($n) => $n + $x; $x = 100; echo $f(1);

  • 6
  • 101
  • 1
  • 105

Answer: 6. The arrow function auto-captured $x by value (5) when defined. Reassigning $x to 100 afterwards has no effect, so 1 + 5 = 6.

What does use(&$x) with the ampersand do?

  • Captures a copy of $x
  • Makes $x read-only
  • Renames $x inside the closure
  • Captures $x by reference, sharing the live variable

Answer: Captures $x by reference, sharing the live variable. use(&$x) captures by reference: the closure shares the same variable as the outer scope and can read and modify the live value.

How do arrow functions (fn() =>) capture outer variables?

  • They require use()
  • They auto-capture by value any variable they reference
  • They auto-capture by reference
  • They cannot capture anything

Answer: They auto-capture by value any variable they reference. Arrow functions automatically capture by value any outer variable they mention — no use() needed.

What is the body limitation of an arrow function?

  • It is limited to a single expression
  • It can have at most 3 statements
  • It cannot take parameters
  • It cannot return a value

Answer: It is limited to a single expression. An arrow function (fn(...) => expression) is limited to one expression, with an implicit return. Use a full closure for multiple statements.

What does echo implode(',', array_map(fn($n) => $n * $n, [1, 2, 3])); print?

  • 1,2,3
  • 2,4,6
  • 1,4,9
  • 1,8,27

Answer: 1,4,9. array_map squares each element with the arrow function: 1, 4, 9.

With $tax = 0.2; $f = function ($p) use ($tax) { return $p + $p * $tax; }; then $tax = 0.5; what is $f(100)?

  • 150
  • 120
  • 100
  • 105

Answer: 120. use($tax) captured a copy of 0.2 at definition time. Changing $tax to 0.5 afterwards does not affect the closure, so 100 + 20 = 120.

Which array helper folds a list down to a single value?

  • array_map
  • array_filter
  • array_merge
  • array_reduce

Answer: array_reduce. array_reduce folds a list into one value (e.g. a running total); array_map transforms and array_filter keeps items.

What does the callable type hint mean?

  • Only a closure is accepted
  • Something you can invoke with (), including closures, function-name strings, and [obj, 'method']
  • An array of functions
  • A class that is instantiable

Answer: Something you can invoke with (), including closures, function-name strings, and [obj, 'method']. callable means 'something you can call like a function' — a closure, a function name string, an [object, 'method'] array, or an invokable object.

After $total = 0; $add = function ($n) use (&$total) { $total += $n; }; $add(3); $add(4); what is $total?

  • 0
  • 3
  • 7
  • 4

Answer: 7. Capturing by reference (&$total) lets the closure mutate the outer variable across both calls: 3 + 4 = 7.

Continue this course

Frequently asked questions

What's the difference between a closure and an arrow function in PHP?

Both are anonymous functions you can store in a variable or pass around. A classic closure (function (...) { ... }) can hold many statements but must explicitly list which outer variables it captures with use(). An arrow function (fn(...) => expression) is limited to a single expression but auto-captures any outer variables it references, by value, with no use() needed. Use an arrow function for short one-liners like callbacks to array_map; use a full closure when you need multiple statements or want to capture by reference.

Why doesn't my closure see a variable from the surrounding code?

PHP closures do not inherit the outer scope automatically — unlike JavaScript. A full closure only sees the variables you explicitly import with use($var). If you forget use(), the variable is simply undefined inside the closure. Arrow functions (fn() =>) are the exception: they automatically capture by value any outer variable they mention, which is exactly why they feel more convenient for quick callbacks.

What does use(&$var) with the ampersand do?

By default use($var) captures a copy of the variable's value at the moment the closure is created, so later changes outside don't affect it. Adding the ampersand, use(&$var), captures by reference: the closure shares the same variable as the outer scope, so it can read and modify the live value, and outside changes are visible inside. This is how you build counters or accumulators that persist state across calls. Use it deliberately — accidental by-reference capture is a common source of confusing bugs.

What is the 'callable' type and how does it relate to closures?

callable is a type hint that means 'something you can call like a function'. A closure or arrow function satisfies callable, but so do a plain function name as a string ('strtoupper'), an array of [object, 'method'], or an invokable object. When you write function run(callable $fn), you're saying 'give me anything I can invoke with ()'. Closures are the most common callable, which is why the two terms often appear together.

Related lessons