PHP Loops

All four PHP loop constructs - for, foreach, while, do-while - with break, continue, nesting tactics, and the foreach-by-reference bug that bites every beginner.

Beginner 7 min read 10 examples

for Loop

Use for when you need precise numeric control:

PHP
<?php
// Standard counter
for ($i = 0; $i < 5; $i++) {
    echo $i . " ";        // 0 1 2 3 4
}

// Count backwards
for ($i = 10; $i >= 1; $i--) {
    echo $i . " ";        // 10 9 8 7 ... 1
}

// Step by 2
for ($i = 0; $i <= 20; $i += 2) {
    echo $i . " ";        // 0 2 4 ... 20
}

// Multiple counters
for ($i = 0, $j = 100; $i < 5; $i++, $j -= 10) {
    echo "$i:$j ";
}

foreach Loop

The default choice for arrays and iterables:

PHP
<?php
$colors = ["red", "green", "blue"];

// Value only
foreach ($colors as $color) {
    echo $color . "\n";
}

// Key + value
foreach ($colors as $index => $color) {
    echo "$index: $color\n";
}

// Associative
$user = ["name" => "Ruban", "age" => 30];
foreach ($user as $field => $value) {
    echo "$field = $value\n";
}

// By reference - modify in place
foreach ($colors as &$color) {
    $color = strtoupper($color);
}
unset($color);   // critical!

// Destructure values inline (PHP 7.1+)
$users = [["alice", 30], ["bob", 25]];
foreach ($users as [$name, $age]) {
    echo "$name is $age\n";
}
The foreach reference trap

If you write foreach ($arr as &$item), ALWAYS follow with unset($item);. The reference outlives the loop and any future assignment to $item silently mutates the last array element. This bug has bitten every PHP developer at least once.

while Loop

Repeat as long as a condition is true. Checks before each iteration:

PHP
<?php
$i = 0;
while ($i < 5) {
    echo $i . " ";
    $i++;
}

// Reading lines from a file
$fh = fopen("data.txt", "r");
while (($line = fgets($fh)) !== false) {
    echo $line;
}
fclose($fh);

// Reading database rows
while ($row = $stmt->fetch()) {
    process($row);
}

do-while Loop

Runs the body at least once, then checks the condition:

PHP
<?php
// Prompt until user enters valid input
do {
    $answer = readline("Continue? (y/n): ");
} while ($answer !== "y" && $answer !== "n");

// Retry with exponential backoff
$attempt = 0;
$result  = null;
do {
    $result = tryRequest();
    if ($result !== null) break;
    sleep(2 ** $attempt);
    $attempt++;
} while ($attempt < 5);

break & continue

PHP
<?php
// break - exit the loop entirely
foreach ($users as $user) {
    if ($user->isBanned()) {
        echo "Banned user found, stopping";
        break;
    }
    sendEmail($user);
}

// continue - skip to next iteration
foreach ($numbers as $n) {
    if ($n < 0) continue;     // skip negatives
    echo sqrt($n) . "\n";
}

// break N - exit nested loops
foreach ($categories as $cat) {
    foreach ($cat->items as $item) {
        if ($item->id === $needle) {
            $found = $item;
            break 2;          // exit BOTH loops
        }
    }
}

Nested Loops

PHP
<?php
// Print a multiplication table
for ($row = 1; $row <= 5; $row++) {
    for ($col = 1; $col <= 5; $col++) {
        printf("%4d", $row * $col);
    }
    echo "\n";
}

// Walk a 2D grid
foreach ($grid as $y => $row) {
    foreach ($row as $x => $cell) {
        if ($cell === "*") echo "Found at ($x, $y)\n";
    }
}
Beware O(n²)

Nested loops over the same dataset are quadratic. With 1,000 items that's 1,000,000 operations. Use a hash map (array_flip()) or pre-indexed lookups instead when possible.

Loop Performance Tips

  1. Hoist count() out of the loop condition: for ($i = 0, $n = count($arr); $i < $n; $i++)
  2. Prefer foreach over for with array access - it's faster.
  3. Use generators for huge datasets instead of building giant arrays.
  4. Avoid foreach by-reference unless you genuinely need to mutate.
  5. array_map/array_filter are not magically faster - foreach is fine.
  6. Break early when you've found what you need.

Next Steps

Frequently Asked Questions

Use foreach almost always - it handles indexed and associative arrays equally well, doesn't need count(), and is faster on associative arrays. Use for only when you need numeric control (e.g., counting backwards, stepping by 2).

You forgot to unset() the reference. foreach ($arr as &$item) leaves $item bound to the last element. The next variable assignment silently mutates the array. Always unset($item); after the loop.

break exits the loop entirely. continue skips the rest of the current iteration and starts the next one. Both accept an integer to break/continue out of multiple nested loops: break 2; exits two levels.

Use foreach normally - generators implement Iterator. They're ideal for huge sequences because they yield one value at a time without loading everything into memory.

Rarely. PHP doesn't optimize tail calls and the default stack limit (~256 frames) is small. Use loops for performance; reserve recursion for naturally tree-shaped problems like directory walking or JSON traversal.