PHPforeach Loop

foreach Loop

foreach is the loop PHP gives you specifically for arrays (and anything that implements the Traversable interface). Where a for loop makes you manage an index counter by hand, foreach walks the array element by element and hands each one to you directly. That makes it the natural choice any time you already have a collection of values and just want to do something with each of them, rather than computing positions or bounds.

Looping over an indexed array

The simplest form reads foreach ($array as $value). On each pass through the loop, $value is set to the next element in the array, in order, until every element has been visited. There is no index variable to maintain and no risk of an off-by-one error at the boundaries.

Iterating a plain indexed array

PHP
<?php
$fruits = ["apple", "banana", "cherry"];

foreach ($fruits as $fruit) {
    echo $fruit . "\n";
}
apple
banana
cherry
Looping over an associative array

Associative arrays store a key alongside every value, and foreach can expose both at once with the as $key => $value form. This is the most common way to read configuration data, form input, or any array that was built with string keys.

Reading keys and values together

PHP
<?php
$profile = [
    "name" => "Sara",
    "role" => "Backend Developer",
    "years" => 4,
];

foreach ($profile as $key => $value) {
    echo "{$key}: {$value}\n";
}
name: Sara
role: Backend Developer
years: 4
list() destructuring for arrays of arrays

A very common shape of data is an array of small "row" arrays — think of results pulled from a database query, or a CSV file parsed into rows. Instead of pulling each column out with $row[0], $row[1], and so on inside the loop body, PHP lets you destructure the row directly in the foreach header using either list() or the equivalent short square-bracket syntax.

Destructuring rows with list() syntax

PHP
<?php
$rows = [
    [1, "Laptop"],
    [2, "Mouse"],
    [3, "Keyboard"],
];

foreach ($rows as [$id, $name]) {
    echo "#{$id} - {$name}\n";
}

// list($id, $name) works exactly the same way
foreach ($rows as list($id, $name)) {
    echo "Item {$id}: {$name}\n";
}
#1 - Laptop
#2 - Mouse
#3 - Keyboard
Item 1: Laptop
Item 2: Mouse
Item 3: Keyboard

Destructuring works for associative sub-arrays too, by matching keys instead of position: foreach ($rows as ["id" => $id, "name" => $name]). This keeps loop bodies short and makes the shape of the data obvious right at the top of the loop, instead of buried in a series of index lookups.

Modifying array elements while looping

By default, $value in a foreach loop is a copy of each array element. Changing $value inside the loop body has no effect on the original array — you are only editing the copy. To actually modify the array in place, PHP lets you loop by reference by prefixing the value variable with an ampersand: foreach ($array as &$value).

Doubling every value in place, by reference

PHP
<?php
$numbers = [1, 2, 3, 4];

foreach ($numbers as &$value) {
    $value *= 2;
}
unset($value);

print_r($numbers);
Array
(
    [0] => 2
    [1] => 4
    [2] => 6
    [3] => 8
)

This works because &$value makes $value an alias for the actual array slot, not a copy of its contents. Every assignment to $value writes straight through to the array itself. Notice the unset($value) line right after the loop — that is not decoration, it is essential, and the next section explains exactly why.

The dangling-reference bug

Here is the pitfall that catches almost every PHP developer at least once. When a foreach ... as &$value loop finishes, PHP does not automatically clean up $value. The variable keeps existing after the loop ends, and it is still a reference pointing at the last element of the array that was just iterated. If you then write a second foreach loop that happens to reuse the same variable name $value — this time by value, with no ampersand — PHP will silently assign into that lingering reference instead of creating a fresh variable. The result is that the last element of the first array gets quietly overwritten with data it was never supposed to have.

Broken: reusing $value after a by-reference loop

PHP
<?php
$numbers = [1, 2, 3, 4];

// Step 1: double every value by reference
foreach ($numbers as &$value) {
    $value *= 2;
}
// $value is STILL a reference to $numbers[3] here — no unset() was called

// Step 2: a completely unrelated loop that happens to reuse "$value"
foreach ($numbers as $value) {
    // On the last pass, $value gets assigned the current element,
    // but because $value is still a reference to $numbers[3],
    // this assignment overwrites $numbers[3] too.
}

print_r($numbers);
Array
(
    [0] => 2
    [1] => 4
    [2] => 6
    [3] => 6
)

Look closely at that output: the last element should be 8 (the doubled value from step 1), but it printed as 6. What happened is that the second foreach loop assigned 2, 4, 6, 8 to $value in turn on each pass, and because $value was still aliased to $numbers[3], every one of those assignments wrote through into the array. By the time the second loop reached its own last element (which by then had already become 8 from the previous write), it assigned 6 into that same slot on its second-to-last pass and nothing overwrote it afterward — leaving corrupted, confusing data that has nothing to do with either loop's intended logic. The exact wrong value depends on the array's length and contents, which is precisely what makes this bug so dangerous: it does not fail loudly, it just produces subtly wrong numbers that are easy to miss in a code review.

Always unset() the reference variable after a by-reference foreach
Any time you write `foreach ($array as &$value) { ... }`, add `unset($value);` on the very next line, before any other code runs. This breaks the reference immediately, so `$value` goes back to being an ordinary variable and cannot silently hijack a later loop that reuses the same name. Skipping this step is one of the most common sources of "my array data is wrong and I don't know why" bugs in PHP codebases, precisely because the corruption happens silently with no warning or error.

Fixed: unset() breaks the reference

PHP
<?php
$numbers = [1, 2, 3, 4];

foreach ($numbers as &$value) {
    $value *= 2;
}
unset($value); // $value is now a normal variable again

foreach ($numbers as $value) {
    // safe: this $value is a fresh copy on every pass,
    // it cannot write back into $numbers
}

print_r($numbers);
Array
(
    [0] => 2
    [1] => 4
    [2] => 6
    [3] => 8
)
Nested foreach loops

Because each foreach uses its own loop variables, nesting them to walk multi-dimensional arrays works cleanly as long as you give the inner and outer loops different variable names — which also happens to sidestep the reference pitfall above, since there is no name collision to worry about.

Iterating a grid of values

PHP
<?php
$grid = [
    [1, 2, 3],
    [4, 5, 6],
];

foreach ($grid as $row) {
    foreach ($row as $cell) {
        echo $cell . " ";
    }
    echo "\n";
}
1 2 3
4 5 6 
Choosing foreach over for or while
  • foreach is the right default whenever you already have a complete array or collection and want to process each element — it removes an entire class of index-related bugs.

  • A for loop is still preferable when you need explicit control over the index itself, such as skipping every other element or iterating in reverse.

  • A while loop fits better when the number of iterations is not known ahead of time and depends on some condition being checked each pass, such as reading from a file until it ends.

  • PHP's built-in array functions (like array_map, array_filter, or array_reduce) are worth considering as an alternative to foreach when the loop body is a simple, single transformation — they can make intent clearer at a glance.

foreach and array pointers
`foreach` operates on a copy of the array's internal structure when looping by value, so it does not disturb the array's internal pointer the way older functions like `each()` (removed in PHP 8) or `current()`/`next()` do. You generally do not need to worry about the internal pointer at all when using `foreach`.
Tip
Make it a habit: the moment you type `as &$value`, type `unset($value);` on the next line before writing the loop body. Doing it in that order means you will never forget it, and it costs nothing when the reference was genuinely only needed inside that one loop.