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
$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
$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
$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
$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
$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.
Fixed: unset() breaks the reference
<?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
$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
foreachis 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
forloop is still preferable when you need explicit control over the index itself, such as skipping every other element or iterating in reverse.A
whileloop 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, orarray_reduce) are worth considering as an alternative toforeachwhen the loop body is a simple, single transformation — they can make intent clearer at a glance.