December Code

PHP functions and closures interview questions, with answers

Modern PHP functions look very different from PHP 5's: arrow functions, named arguments, union types and first-class callables have all arrived since PHP 7.4. Interviews check that you can use them, and that you understand the scoping rules underneath, especially how closures capture variables from the code around them.

The answers below cover closures and arrow functions, parameters, references, type declarations, callables and static variables, with code run on PHP 8.5. Then take the free PHP diagnostic — ten questions across every PHP topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.How do closures capture variables in PHP?

    In short: An anonymous function sees outside variables only if they are listed in its use clause, which copies their values when the closure is created.

    Unlike JavaScript, a PHP anonymous function does not see the variables around it automatically. The use clause names the variables to capture, and by default it copies each value at the moment the closure is created, so $n below is 2 inside the closure whatever happens to the outer $n later. Prefixing a variable with & in the use clause captures it by reference, so the closure and the outer code share it. Closures are objects of the Closure class, which can be stored, passed to functions such as array_map and bound to an object with bind.

    $n = 2;
    $f = function ($x) use ($n) {
        return $x * $n;
    };
    echo $f(5);
    // 10
  2. 2.What are arrow functions in PHP?

    In short: fn($x) => expression is a short closure, added in PHP 7.4, that returns one expression and captures outside variables by value automatically.

    An arrow function is written fn(parameters) => expression. Its body is a single expression, whose value is returned, and any outside variable it mentions is captured by value automatically, so fn($x) => $x * $k needs no use clause. That makes them ideal for short callbacks passed to array_map, array_filter and usort. Because the body is one expression, anything needing statements, such as a loop or several assignments, still requires a full function () use (...) closure.

    $k = 3;
    $mul = fn($x) => $x * $k;
    echo $mul(4);
    // 12
  3. 3.How do default and named arguments work in PHP functions?

    In short: Parameters can have default values, and since PHP 8 arguments can be passed by name, in any order, skipping optional ones.

    A parameter with a default value, such as string $end = "!", may be left out of a call. Before PHP 8, reaching a later optional parameter meant passing every earlier one; named arguments, added in PHP 8, let the caller write greet(end: "?", name: "Li") in any order and skip what it does not need. Named arguments make calls to functions with several optional flags readable. The parameter names become part of the function's public interface, so renaming a parameter can break callers that use its name.

    function greet(
        string $name,
        string $end = "!",
    ): string {
        return "Hi $name$end";
    }
    
    echo greet("Ana"), " ";
    echo greet(end: "?",
        name: "Li");
    // Hi Ana! Hi Li?
  4. 4.How do you pass an argument by reference in PHP?

    In short: Prefix the parameter with &, as in function f(array &$a), so the function works on the caller's variable instead of a copy.

    PHP passes arguments by value, so a function normally receives its own copy of a scalar or an array. Declaring a parameter with &, as addOne(array &$a) does, binds it to the caller's variable, so appending inside the function changes $list, and count is 1 afterwards. Several built-in functions work this way, such as sort, which sorts the array it is given. References make data flow harder to follow, so returning a new value is usually clearer; they are mainly used for large arrays and for functions that must update several values.

    function addOne(array &$a) {
        $a[] = 1;
    }
    $list = [];
    addOne($list);
    echo count($list);
    // 1
  5. 5.How do type declarations work for PHP function parameters and return values?

    In short: Parameters and return values can declare types, including nullable ?T and union types such as int|float, which PHP checks on every call.

    A declaration such as function half(int|float $n): float states what the function accepts and returns. PHP checks it when the function is called and when it returns, and throws a TypeError for a value that does not fit; in the default mode, scalar arguments are converted where possible, so an int passed where a float is declared becomes a float. ?string allows null as well as a string, union types arrived in PHP 8.0, and mixed, void, never and static cover the special cases. Declarations document the contract and let static analysers catch mistakes before the code runs.

    function half(
        int|float $n,
    ): float {
        return $n / 2;
    }
    
    var_dump(half(3));
    // float(1.5)
  6. 6.What are first-class callables in PHP?

    In short: strlen(...) turns a named function or method into a Closure object, the PHP 8.1 replacement for passing function names as strings.

    PHP functions that take callbacks have long accepted a function's name as a string, such as "strtoupper", or an array such as [$obj, "method"]. PHP 8.1 added first-class callable syntax: writing a call with ... as its only argument, as in strlen(...) or $obj->method(...), creates a Closure for that function. Unlike a string, it is checked when it is created, refactoring tools can follow it, and static analysers know its signature. The resulting closure is called like any other, so $len("four") returns 4.

    $len = strlen(...);
    echo $len("four"), " ";
    echo implode(",", array_map(
        "strtoupper", ["a", "b"]));
    // 4 A,B
  7. 7.What does a static variable inside a PHP function do?

    In short: It keeps its value between calls: it is initialised once, on the first call, instead of being reset every time the function runs.

    A local variable normally disappears when the function returns. Declaring it static, as static $id = 0, makes PHP initialise it once and keep its value across calls, so nextId returns 1 on the first call and 2 on the second. Static variables are useful for simple counters and for caching a computed result inside a function. Since PHP 8.3 the initial value can be any expression, including a function call. Because they are hidden state, they make a function harder to test, so a class property is often the cleaner choice.

    function nextId() {
        static $id = 0;
        return ++$id;
    }
    nextId();
    echo nextId();
    // 2

How the diagnostic asks it

One question from the PHP bank, exactly as a sitting would show it. The bank has 4 on functions & closures and 30 across PHP.

Functions & Closures · easyPHP-012

What does this PHP code print?

$x = 1;
$f = function () use ($x) {
    return $x;
};
$x = 2;
echo $f();
  1. 12
  2. 2It throws an error: $x is undefined inside the closure
  3. 3Nothing, because the closure returns null
  4. 41correct

An anonymous function sees outside variables only through its use clause, and use ($x) copies the value of $x at the moment the closure is created, which is 1. Changing $x to 2 afterwards does not affect the copy, so $f() returns 1. 2 would need capture by reference, use (&$x), which binds the closure to the variable itself. Without the use clause, $x would be undefined inside the closure, but here it is listed. The closure returns the captured 1, not null. Arrow functions, fn() => $x, capture by value automatically, in the same way.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 PHP questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated