Lambda vs closure vs arrow function — the exact differences
Intermediate5 min read·eng-12-012
interviewcompare
Concept
Lambda vs closure vs arrow function — three terms for anonymous functions in PHP, with precise differences.
Anonymous function (legacy term, pre-PHP 5.3): A function without a name. In PHP, function() { } is the syntax. The Closure class instance is what PHP creates.
Closure (PHP term): PHP's Closure class is what anonymous functions ARE. When you write $fn = function() { }, $fn is a Closure instance. Closures can capture variables from the outer scope using use ($var).
use ($var): Capture by value (copy at the time of closure creation).use (&$var): Capture by reference (shares the variable).- Inside a closure,
$thisis NOT automatically available unless the closure is bound to an object.
Lambda (computer science term): A function value — a function that can be stored in a variable and passed around. In PHP, all anonymous functions are lambdas in this sense. The terms are often used interchangeably.
Arrow function (PHP 7.4+, fn() =>):
- Single expression, no
{}block. - Automatically captures variables from the outer scope (no
usekeyword needed). - Cannot have a return type declaration (though type inference works).
- Cannot span multiple lines (except with complex expressions).
- Returns the expression value implicitly (no
returnkeyword). - Cannot capture by reference (no
use (&$var)equivalent).
When to use which:
- Multi-line logic →
function() { ... }(regular closure). - Single expression with outer variables →
fn($x) => expression(arrow function — cleaner). - Named, reusable function → regular function or method.
Code Example
php
<?php
// Regular closure — multi-line, explicit capture
$multiplier = 3;
$multiply = function(int $n) use ($multiplier): int {
// $multiplier captured by value at closure creation time
return $n * $multiplier;
};
echo $multiply(5); // 15
$multiplier = 10; // changing outer variable...
echo $multiply(5); // still 15 — captured by VALUE (copy)
// Capture by reference — shares the variable
$count = 0;
$counter = function() use (&$count): void { $count++; };
$counter(); $counter(); $counter();
echo $count; // 3 — modified via reference
// Arrow function — automatic capture, single expression
$prefix = 'Hello';
$greet = fn(string $name): string => "{$prefix}, {$name}!"; // no 'use' needed
echo $greet('Alice'); // "Hello, Alice!"
$prefix = 'Goodbye';
echo $greet('Alice'); // "Goodbye, Alice!" — arrow fn captures the variable itself (NOT a copy!)
// Wait — arrow functions capture by VALUE too, but the value is re-evaluated each call? NO.
// Arrow functions capture at definition time, by value. Let me clarify:
// Actually arrow functions capture by VALUE at DEFINITION time (same as 'use ($var)')
// $prefix was 'Hello' when $greet was defined, so it's always 'Hello'
// More accurate example:
$tax = 0.1;
$prices = [100, 200, 300];
$withTax = array_map(fn($p) => $p * (1 + $tax), $prices); // [110, 220, 330]
// vs regular closure:
$withTax2 = array_map(function($p) use ($tax) { return $p * (1 + $tax); }, $prices);
// Closure bound to an object
class Counter
{
private int $count = 0;
public function getIncrementer(): \Closure
{
return function() { $this->count++; return $this->count; }; // $this available here
}
}
$counter = new Counter();
$inc = $counter->getIncrementer();
echo $inc(); // 1
echo $inc(); // 2
// Closure::bind() — bind a closure to a different object
$fn = Closure::bind(function() { return $this->count; }, $counter, Counter::class);
echo $fn(); // 2
// Practical: array functions with arrow functions (cleaner)
$users = [['name' => 'Alice', 'age' => 25], ['name' => 'Bob', 'age' => 30]];
$names = array_map(fn($u) => $u['name'], $users); // ['Alice', 'Bob']
$adults = array_filter($users, fn($u) => $u['age'] >= 18);
$youngest = array_reduce($users, fn($min, $u) => $u['age'] < ($min['age'] ?? PHP_INT_MAX) ? $u : $min);