0

References in PHP — &$var, how they work in memory

Intermediate5 min read·php-02-016
interviewcompare

Concept

A reference in PHP is an alias — two variable names pointing to the same underlying zval container in memory. When you write $b = &$a, you're not copying the value; both $a and $b now refer to the same memory location. Changes through either name are immediately visible through the other.

References are fundamentally different from pointers in C. PHP's reference system sits on top of the zval reference-counting engine. A reference increments a zval's refcount but also sets its is_ref flag, which changes how copy-on-write behaves. A normal assignment ($b = $a) uses COW — the copy is deferred until a write occurs. A reference assignment ($b = &$a) disables COW entirely: both variables always share the same storage.

Where references genuinely help: passing large arrays into functions you want to mutate in-place, or returning multiple values without returning an array. Where they hurt: they break copy-on-write, making PHP do unexpected copies elsewhere, and they make code harder to reason about. Laravel's codebase almost never uses references; prefer returning values.

Reference pitfalls: The classic bug is foreach ($arr as &$item) — after the loop, $item still points to the last element. Any subsequent write to $item silently mutates the array. Always unset($item) after a reference-based foreach.

Code Example

php
<?php
declare(strict_types=1);

// Basic reference — both names share one zval
$a = 'hello';
$b = &$a;
$b = 'world';
echo $a; // "world" — $a was mutated through $b

// Reference in function — modify caller's variable
function addPrefix(string &$str, string $prefix): void
{
    $str = $prefix . $str;
}
$name = 'PHP';
addPrefix($name, 'Modern ');
echo $name; // "Modern PHP"

// The classic foreach reference bug
$items = ['a', 'b', 'c'];
foreach ($items as &$item) {
    $item = strtoupper($item);
}
// $item is still a reference to $items[2]
unset($item); // ALWAYS unset after reference foreach

// Safe alternative — no reference needed
$items = array_map('strtoupper', $items);

// Returning by reference (rare, usually a design smell)
class Config
{
    private array $data = ['debug' => false];

    public function &get(string $key): mixed
    {
        return $this->data[$key];
    }
}
$cfg = new Config();
$debug = &$cfg->get('debug');
$debug = true; // modifies $cfg->data['debug'] directly

Interview Q&A

Q: What is the difference between passing by value and passing by reference in PHP, and when would you use a reference?

By value (the default), PHP passes a copy of the variable to the function — with copy-on-write ensuring the actual copy only happens if the function writes to it. By reference (&$param), the function receives an alias to the caller's variable; writes inside the function mutate the caller's scope directly. Use references sparingly: when you genuinely need to mutate a caller's variable in-place (e.g., a recursive tree builder accumulating results) or when passing a very large data structure and profiling confirms the COW overhead is significant. In practice, returning a value is almost always cleaner.


Q: Why does foreach ($arr as &$item) cause bugs if you don't unset $item afterward?

After the loop completes, the loop variable $item is a live reference to the last element of $arr. If you later assign anything to $item — even in an unrelated loop — you silently overwrite the last array element. For example: foreach ($other as $item) would clobber $arr's last entry on every iteration. The fix is unset($item) immediately after the loop, which breaks the reference binding. This is one of PHP's most notorious footguns.


Q: How does PHP's is_ref flag interact with copy-on-write, and why can references break performance?

Normally PHP uses copy-on-write: multiple variables share a zval, and the engine only physically copies the data when one of them is written. When a reference is created, the zval's is_ref flag is set, which disables COW — the engine can no longer safely share this zval between a reference and a non-reference variable. If you then assign the referenced variable to a new variable normally ($c = $a where $a is a ref), PHP must immediately separate the zvals, defeating COW and causing an allocation. This means references in hot loops or on large arrays can actually increase memory pressure rather than reduce it.