PHP For Loop

The for loop is PHP’s go-to tool when you know — or can calculate — exactly how many times you need to repeat a block of code. Instead of writing the same line ten times, you write one loop that counts, checks a condition, and updates itself automatically on every pass. It’s one of the first control structures every PHP developer learns, and understanding it deeply pays off, because while loops, foreach loops, and even generators build on the very same iteration concepts.

Overview: How the For Loop Works

A for loop bundles three separate steps — initialization, condition checking, and updating — into a single, compact statement. PHP evaluates these three expressions at very specific points in the loop’s lifecycle, and understanding that timing is the key to mastering the loop and avoiding bugs.

Internally, the Zend Engine (PHP’s execution engine) compiles a for loop into essentially the same bytecode you would get from writing a while loop by hand with an extra statement tacked onto the end of every pass. Conceptually, PHP performs these steps in order:

  1. Run the initialization expression exactly once, before the loop starts.
  2. Check the condition expression. If it evaluates to false (after being cast to a boolean), the loop ends immediately and execution jumps to the code after the loop.
  3. If the condition is true, execute the loop’s body — the statement or block of statements.
  4. Run the increment/update expression.
  5. Go back to step 2 and repeat the whole cycle.

Because the condition is checked before the body runs, a for loop is a pre-test loop: if the condition is false the very first time, the body never executes — not even once. This is different from a do...while loop, which always runs its body at least one time before checking anything.

Another important detail: variables declared in the initialization expression (like a counter $i) are ordinary variables in the surrounding scope. PHP does not create a fresh block scope for the loop the way languages like JavaScript’s let do. That means the counter still exists, holding its final value, after the loop finishes — which is convenient sometimes, but can also cause subtle bugs if you reuse the same variable name later in the same function.

break and continue interact with the three-part structure in a specific way that trips up beginners coming from a purely conceptual understanding of loops: continue does not jump straight back to the condition — it jumps to the increment expression first, which then runs before the condition is re-checked. break, on the other hand, exits the loop immediately, skipping both the increment and the condition entirely. In nested loops, break 2; or continue 2; can be used to affect an outer loop by number of levels.

Syntax

The general form of a for loop is:

for (initialization; condition; increment) {
    // code to repeat
}
Part Runs When Purpose
initialization Once, before the loop starts Sets up the counter variable(s), e.g. $i = 0
condition Before every iteration Decides whether to keep looping; evaluated as a boolean
increment After every iteration’s body Updates the counter, e.g. $i++
body Once per iteration, only if condition is true The statement(s) to repeat

Any of the three expressions may be left empty (the semicolons are still required), and each slot can actually hold multiple expressions separated by commas — PHP evaluates them left to right. That is exactly what makes for ($i = 0, $j = 10; $i < $j; $i++, $j--) valid PHP. Leaving all three empty, as in for (;;) { ... }, creates an intentional infinite loop that you would normally pair with a break statement inside the body.

PHP also supports an alternative colon-based syntax, which is popular when mixing PHP with HTML in templates:

for ($i = 0; $i < 5; $i++):
    echo $i;
endfor;

Examples

Example 1: Counting With a Simple Loop

<?php
for ($i = 1; $i <= 5; $i++) {
    echo "Number: $i" . PHP_EOL;
}

Output:

Number: 1
Number: 2
Number: 3
Number: 4
Number: 5

This is the classic counting loop. $i starts at 1, the loop keeps running as long as $i is less than or equal to 5, and $i++ increases the counter by one after each print. Notice the loop stops after printing 5, because once $i becomes 6 the condition 6 <= 5 is false.

Example 2: Iterating Over an Array by Index

<?php
$fruits = ['Apple', 'Banana', 'Cherry', 'Date'];
for ($i = 0; $i < count($fruits); $i++) {
    echo ($i + 1) . ". " . $fruits[$i] . PHP_EOL;
}

Output:

1. Apple
2. Banana
3. Cherry
4. Date

Here the loop uses count($fruits) as the upper bound so it automatically adapts if the array grows or shrinks. Because array indexes are zero-based but we want a human-friendly numbered list, the code adds 1 only when printing, while $i itself is still used to access $fruits[$i]. For simple sequential arrays like this, a foreach loop is usually cleaner, but a for loop is essential whenever you need the numeric index itself, or need to skip, reverse, or step through elements in a non-standard order.

Example 3: FizzBuzz — Combining a For Loop With Conditionals

<?php
for ($i = 1; $i <= 15; $i++) {
    if ($i % 15 === 0) {
        echo "FizzBuzz" . PHP_EOL;
    } elseif ($i % 3 === 0) {
        echo "Fizz" . PHP_EOL;
    } elseif ($i % 5 === 0) {
        echo "Buzz" . PHP_EOL;
    } else {
        echo $i . PHP_EOL;
    }
}

Output:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

This well-known exercise shows how a for loop's body can contain any valid PHP statement, including nested conditionals. On each pass the modulo operator % checks divisibility, and the loop itself doesn't care what happens inside the body — it just keeps calling it until the condition becomes false.

Example 4: Multiple Expressions With the Comma Operator

<?php
for ($i = 5, $j = 1; $i > 0; $i--, $j *= 2) {
    echo "i=$i, j=$j" . PHP_EOL;
}

Output:

i=5, j=1
i=4, j=2
i=3, j=4
i=2, j=8
i=1, j=16

This demonstrates that each of the three slots in a for statement can hold more than one expression, separated by commas. Here two variables are initialized together, and both are updated together after every iteration — $i counts down while $j doubles. This pattern is handy whenever a loop needs to track two related values in lockstep without nesting a second loop.

How It Works Step by Step (Under the Hood)

Let's trace Example 1 exactly the way the Zend Engine does:

  1. Initialization runs once: $i = 1 is executed. This never happens again for the rest of the loop.
  2. Condition check #1: Is 1 <= 5? Yes. The engine proceeds to the body.
  3. Body runs: echo "Number: 1" is executed.
  4. Increment runs: $i++ makes $i equal to 2.
  5. Condition check #2: Is 2 <= 5? Yes. Body runs, printing "Number: 2", then increment makes $i equal to 3.
  6. This cycle of condition → body → increment repeats for $i = 3, 4, and 5.
  7. Final condition check: After the body runs for $i = 5, increment sets $i to 6. The engine checks 6 <= 5, which is false, so it exits the loop without running the body again.

After the loop, $i still holds the value 6 — the value that made the condition fail — because, as noted earlier, PHP does not scope the counter to the loop. This is a useful debugging fact: if you're not sure how many times a loop actually ran, you can often infer it from the counter's final value.

Common Mistakes

Mistake 1: Off-by-One Errors From the Wrong Comparison Operator

It's extremely easy to use <= when you meant < (or vice versa), producing one extra or one missing iteration.

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

Output:

0 1 2 3 4 5 

The developer intended to print five numbers (0 through 4), but <= allows $i to reach 5 as well, producing six numbers instead of five. The fix is to use a strict less-than comparison when counting a fixed number of items starting from zero:

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

Output:

0 1 2 3 4 

A useful rule of thumb: when counting zero-based array indexes or a total of N items, pair $i = 0 with $i < N, not $i <= N.

Mistake 2: A Stray Semicolon After the For Statement

Because PHP allows an empty statement (just ;) as a loop body, accidentally adding a semicolon right after the closing parenthesis silently turns the intended body into a no-op, and the block that follows runs only once, completely outside the loop.

<?php
for ($i = 0; $i < 5; $i++);
{
    echo "i is $i" . PHP_EOL;
}

Output:

i is 5

The loop itself runs correctly five times, incrementing $i up to 5, but its body is just the empty statement before the semicolon — the { ... } block is a separate, unrelated block of code that executes exactly once, after the loop is already finished, printing whatever $i ended up being. The fix is simply to remove the semicolon so the braces become the loop's actual body:

<?php
for ($i = 0; $i < 5; $i++) {
    echo "i is $i" . PHP_EOL;
}

Output:

i is 0
i is 1
i is 2
i is 3
i is 4

This bug is dangerous precisely because it doesn't cause a parse error or a warning — the code runs, just not the way you expect. Always double-check that there is no semicolon between a for(...) header and its opening brace.

Best Practices

  • Use for when you need a numeric counter or index; use foreach when you simply need each element of an array or iterable — it's shorter and avoids off-by-one mistakes entirely.
  • Prefer strict comparisons (<, >) over <=/>= when counting a known number of items starting from zero, to keep the math intuitive.
  • Cache values that don't change across iterations — for example, compute $total = count($items); once before the loop instead of calling count($items) in the condition on every pass.
  • Avoid modifying the loop counter inside the body in addition to the increment expression; doing both causes confusing, hard-to-spot skipped iterations.
  • Keep the loop body short and readable; if it grows past a few lines, extract the logic into a well-named function.
  • Use meaningful variable names for nested loops ($row/$col instead of $i/$j) so the code's intent is clear at a glance.
  • When a loop truly has no natural end condition, make that explicit with for (;;) { ... } plus a clear break, rather than relying on a condition that happens to never become false.

Practice Exercises

  • Exercise 1: Write a for loop that prints all even numbers from 2 to 20 (inclusive), each on its own line.
  • Exercise 2: Given $colors = ['red', 'green', 'blue', 'yellow'];, use a for loop to print the array in reverse order, one color per line, starting from the last element.
  • Exercise 3: Use a nested for loop to print a multiplication table for the numbers 1 through 5 (5 rows, 5 columns), formatting each row as space-separated products, e.g. the first row should read 1 2 3 4 5.

Summary

  • A for loop combines initialization, condition, and increment into one header, and repeats its body only while the condition is true.
  • The condition is checked before the body runs, so the body can execute zero times.
  • Each of the three expressions can be empty or contain multiple comma-separated expressions.
  • The loop counter is not scoped to the loop — it remains accessible, with its final value, after the loop ends.
  • continue jumps to the increment expression, not directly to the condition; break skips both.
  • Common bugs include off-by-one comparison operators and an accidental semicolon right after the for(...) header.
  • Prefer foreach for simple array iteration; reach for for when you need explicit control over the index or counter.