Nested loops and typical mistakes
The coach sets a task: three sets, each of three laps. The runner runs the three laps of the first set, then the three laps of the second, then of the third. That makes three times three, that is, nine laps.
A loop inside another loop is called nested. The inner loop runs all its iterations on each iteration of the outer one. This is a handy way to build tables — for example, a multiplication table:
<?php
for ($row = 1; $row <= 3; $row++) {
$line = '';
for ($col = 1; $col <= 3; $col++) {
$line .= $row * $col . ' ';
}
echo $line, "\n";
}
// 1 2 3
// 2 4 6
// 3 6 9Let's see in what order the loops run:
- The outer loop goes through the rows:
$rowfrom1to3. - At the start of each row,
$line = ''sets up an empty string. - The inner loop goes through the columns:
$colfrom1to3. On each iteration, it appends the product and a space to$line. - When the inner loop has finished,
echoprints the finished row, and the outer loop moves on to the next one.
The body of the inner loop runs nine times here. Interviewers often ask how many times the body of a nested loop runs: multiply the outer loop's iterations by the inner loop's.
Here the training picture is inexact: a runner gets tired, a loop does not, and nesting multiplies the work without anyone noticing. Two loops of a thousand iterations each already make a million runs of the body.
A row in one string
The row is built up in the variable $line piece by piece. As you already know, the .= operator appends a string to the end: after the first column $line holds '1 ', after the second '1 2 '.
Where $line = '' stands matters: inside the outer loop, before the inner one. That way each row starts with an empty string. If you move this line above the outer loop, the rows stick together, and the second echo prints 1 2 3 2 4 6.
Building a row in a string is handier than printing each number with its own echo. The line break "\n" is needed only once, at the end of the row.
Leaving two loops
A break in the inner loop ends only that loop, and the outer loop moves on to the next row. To leave both at once, write break 2: the number says how many nested loops to leave. This code looks for the first cell in the table whose value is 6:
for ($row = 1; $row <= 3; $row++) {
for ($col = 1; $col <= 3; $col++) {
if ($row * $col === 6) {
break 2;
}
}
}
echo $row, ' ', $col, "\n"; // 2 3Let's go through it step by step:
- The first row holds
1,2and3; there is no six. - In the second row, when
$colis3, the product is6, andbreak 2ends both loops. - The steps
$col++and$row++no longer run, soechoprints the cell that was found:2 3.
With a plain break, the outer loop would go on searching. It would also find 3 * 2, end with $row equal to 4, and the program would print 4 2.
A forgotten counter step
Nested loops have two counters, and sometimes the step is written for the wrong one. In the header for ($col = 1; $col <= 3; $row++), the step changes $row instead of $col. Then $col stays 1 forever, and the inner loop never ends.
In the multiplication table such a program does not last long: the body keeps appending to $line, and the string grows until it fills all the memory PHP allows. Then PHP stops it with the message Fatal error: Allowed memory size of 134217728 bytes exhausted. Check the header: all three parts of for hold the same name.
Common mistake
The inner loop got < instead of <=:
for ($row = 1; $row <= 3; $row++) {
$line = '';
for ($col = 1; $col < 3; $col++) { // ✗
$line .= $row * $col . ' ';
}
echo $line, "\n";
}The program runs without messages, but each row has two numbers instead of three: 1 2, 2 4 and 3 6. The condition $col < 3 is already false when $col is 3, and the third column disappears. Such a mistake is called an off-by-one error: the loop makes one iteration more or fewer than needed.
The first and the last iteration help you check the boundaries:
$i = 1; $i <= $nruns$niterations, from1to$n;$i = 0; $i < $nalso runs$niterations, from0to$n - 1— this is how a list's indexes are walked.
Key points
- The inner loop runs all its iterations on each iteration of the outer one; the total is their product.
- A row is handy to build in a string with
.=and print once, starting each row with$line = ''. breakleaves only the inner loop,break 2leaves both at once.<and<=differ by one iteration: check the first and the last one.