Nested Loops

JA JASWANT Updated 17 Oct 2026
8 min read ·Lesson 10 of 248
In this post

What Is a Nested Loop? In simple terms: A nested loop is a loop inside another loop. For each iteration of the outer loop, the inner loop runs completely from start to finish. Technically: Any loop (

What Is a Nested Loop?

In simple terms: A nested loop is a loop inside another loop. For each iteration of the outer loop, the inner loop runs completely from start to finish.

Technically: Any loop (for, foreach, while, do...while) can contain another loop in its body. If the outer loop runs m times and the inner loop runs n times per outer iteration, the inner body executes m × n times.

Why Nested Loops Are Needed

Many kinds of data have two (or more) dimensions:

  • Rows and columns of a table or grid
  • Orders, each with several items
  • Categories, each with several products
  • Every combination of sizes and colors

A nested loop visits each item in such structures.


Syntax

foreach ($outerItems as $outer) {
    // runs once per outer item

    foreach ($innerItems as $inner) {
        // runs once per combination of $outer and $inner
    }
}

Example: Visualizing the Order of Execution

<?php
for ($row = 1; $row <= 3; $row++) {
    for ($col = 1; $col <= 2; $col++) {
        echo "row $row, col $col\n";
    }
}

Output

row 1, col 1
row 1, col 2
row 2, col 1
row 2, col 2
row 3, col 1
row 3, col 2

How It Works

  1. The outer loop starts with $row = 1.
  2. The inner loop runs completely: $col = 1, then $col = 2.
  3. Only then does the outer loop move to $row = 2, and the inner loop starts again from $col = 1.
  4. Total iterations of the inner body: 3 × 2 = 6.

Think of reading a book: for each line (outer loop), you read every word on that line (inner loop) before moving to the next line.


Practical Example: Multiplication Table

<?php
$size = 4;

for ($i = 1; $i <= $size; $i++) {
    for ($j = 1; $j <= $size; $j++) {
        printf("%4d", $i * $j);
    }
    echo "\n";
}

Output

   1   2   3   4
   2   4   6   8
   3   6   9  12
   4   8  12  16
  • The outer loop selects the row number $i.
  • The inner loop prints one cell per column $j, each value right-aligned in 4 characters with %4d.
  • The echo "\n" is placed after the inner loop but inside the outer loop, so a line break is printed at the end of each row.

Where you place code — inside the inner loop, between the loops, or after them — determines how often it runs.


Practical Example: Orders and Their Items

Real data is often a list of records where each record contains its own list:

<?php
$orders = [
    [
        'id' => 501,
        'items' => [
            ['name' => 'Notebook', 'qty' => 3, 'price' => 60],
            ['name' => 'Pen', 'qty' => 10, 'price' => 15],
        ],
    ],
    [
        'id' => 502,
        'items' => [
            ['name' => 'Backpack', 'qty' => 1, 'price' => 1299],
        ],
    ],
];

foreach ($orders as $order) {
    echo "Order #{$order['id']}\n";
    $orderTotal = 0;

    foreach ($order['items'] as $item) {
        $lineTotal = $item['qty'] * $item['price'];
        $orderTotal += $lineTotal;
        printf("  %-10s %2d × ₹%-5d = ₹%d\n", $item['name'], $item['qty'], $item['price'], $lineTotal);
    }

    echo "  Total: ₹$orderTotal\n";
}

Output

Order #501
  Notebook    3 × ₹60    = ₹180
  Pen        10 × ₹15    = ₹150
  Total: ₹330
Order #502
  Backpack    1 × ₹1299  = ₹1299
  Total: ₹1299

How It Works

  • The outer loop visits each order and resets $orderTotal to 0 for that order.
  • The inner loop visits each item of the current order ($order['items']) and adds its line total.
  • After the inner loop finishes, the total for that order is printed.

Placing $orderTotal = 0; inside the outer loop (but before the inner loop) is crucial. If it were before the outer loop, totals would accumulate across orders.


Practical Example: All Combinations

Nested loops generate every combination of two lists — for example, product variants:

<?php
$sizes = ['S', 'M', 'L'];
$colors = ['Red', 'Blue'];
$skus = [];

foreach ($sizes as $size) {
    foreach ($colors as $color) {
        $skus[] = 'TSHIRT-' . $size . '-' . strtoupper($color);
    }
}

echo implode("\n", $skus) . "\n";
echo count($skus) . " variants\n";

Output

TSHIRT-S-RED
TSHIRT-S-BLUE
TSHIRT-M-RED
TSHIRT-M-BLUE
TSHIRT-L-RED
TSHIRT-L-BLUE
6 variants

3 sizes × 2 colors = 6 variants. Adding a third dimension (for example, sleeve length) would require a third nested loop.


Practical Example: Drawing a Pattern

Pattern exercises demonstrate how the inner loop can depend on the outer loop's counter:

<?php
$height = 4;

for ($row = 1; $row <= $height; $row++) {
    for ($space = 1; $space <= $height - $row; $space++) {
        echo ' ';
    }

    for ($star = 1; $star <= 2 * $row - 1; $star++) {
        echo '*';
    }

    echo "\n";
}

Output

   *
  ***
 *****
*******

How It Works

RowSpaces (height - row)Stars (2 × row - 1)
131
223
315
407

The number of inner iterations is calculated from the outer counter. In production code, str_repeat(' ', $height - $row) . str_repeat('*', 2 * $row - 1) produces the same line without inner loops.


Performance: The Cost of Nesting

The inner body of nested loops runs outer count × inner count times:

Outer ItemsInner ItemsInner Iterations
1010100
1,0001,0001,000,000
10,00010,000100,000,000

Nested loops over small lists are fine. Over large lists — especially when the inner loop only searches for a matching item — they become a serious performance problem.

Slow: Searching in an Inner Loop

<?php
$customers = [
    ['id' => 1, 'name' => 'Asha'],
    ['id' => 2, 'name' => 'Bala'],
];

$orders = [
    ['id' => 101, 'customer_id' => 2, 'total' => 500],
    ['id' => 102, 'customer_id' => 1, 'total' => 1200],
    ['id' => 103, 'customer_id' => 2, 'total' => 300],
];

foreach ($orders as $order) {
    foreach ($customers as $customer) {
        if ($customer['id'] === $order['customer_id']) {
            echo "Order {$order['id']}: {$customer['name']} (₹{$order['total']})\n";
            break;
        }
    }
}

Fast: Build a Lookup Array Once

<?php
$customerNames = array_column($customers, 'name', 'id');   // [1 => 'Asha', 2 => 'Bala']

foreach ($orders as $order) {
    $name = $customerNames[$order['customer_id']] ?? 'Unknown customer';
    echo "Order {$order['id']}: $name (₹{$order['total']})\n";
}

Output (both versions)

Order 101: Bala (₹500)
Order 102: Asha (₹1200)
Order 103: Bala (₹300)

Why the Second Version Scales

  • array_column($customers, 'name', 'id') creates an array where each customer's id is the key and the name is the value.
  • Looking up an array element by key is a constant-time operation, regardless of the array's size.
  • The work becomes orders + customers instead of orders × customers. With 10,000 orders and 10,000 customers, that is about 20,000 operations instead of up to 100 million.

In real applications, this kind of matching is usually done by the database with a SQL JOIN (see MySQL Joins with PHP).


Controlling Nested Loops

break and continue affect the innermost loop by default. A numeric argument targets outer loops:

StatementEffect
breakExit the inner loop
break 2Exit the inner and the outer loop
continueSkip to the next iteration of the inner loop
continue 2Skip to the next iteration of the outer loop
<?php
$seatMap = [
    'A' => [1, 1, 1, 1],   // 1 = taken, 0 = free
    'B' => [1, 1, 0, 1],
    'C' => [0, 0, 0, 0],
];

$found = null;

foreach ($seatMap as $row => $seats) {
    foreach ($seats as $index => $taken) {
        if ($taken === 0) {
            $found = $row . ($index + 1);
            break 2;
        }
    }
}

echo "First free seat: " . ($found ?? 'none') . "\n";

Output

First free seat: B3

As soon as a free seat is found, break 2 stops both loops, so no further seats are checked. Details are in Break Statement and Continue Statement.


Common Mistakes

Reusing the Same Counter Variable

for ($i = 0; $i < 3; $i++) {
    for ($i = 0; $i < 2; $i++) { ... }   // overwrites the outer $i
}

Effect: The inner loop resets the outer counter, causing an infinite loop or skipped iterations.

Fix: Use different names ($row/$col, $i/$j, $order/$item).

Resetting Accumulators in the Wrong Place

Effect: Totals accumulate across outer items, or reset too often.

Fix: Initialize per-outer-item values inside the outer loop, before the inner loop.

Searching With Nested Loops Over Large Data

Effect: Very slow code.

Fix: Build lookup arrays with array_column() or keys, or let the database join the data.

Deep Nesting

Effect: Code with three or more nested loops is hard to read.

Fix: Move inner loops into well-named functions, such as calculateOrderTotal($order).


Best Practices

  • Use clear, distinct variable names for each loop level.
  • Place each statement at the level where it belongs: per row, per cell, or once overall.
  • Replace "search in inner loop" patterns with lookup arrays.
  • Use break 2 / continue 2 to control outer loops when needed.
  • Extract inner loops into functions when the logic grows.

Official reference: https://www.php.net/manual/en/control-structures.for.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings Function Arguments PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro PHP Class Constants Encryption with Sodium Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions array_map / array_filter / array_reduce Array Functions PHP Superglobals $GLOBALS What Is `$_SERVER` ? $_REQUEST $_POST $_GET $_FILES $_COOKIE PHP Type Juggling MySQL Order By MySQL Delete Data MySQL Update Data MySQL Limit Data MySQL Joins with PHP Pagination with PHP PDO Introduction PDO Connect PDO Prepared Statements PDO Fetch Modes PSR-4 Autoloading Installing Extensions (PECL / PIE) OPcache & Performance PHP-FPM Project: Contact Form with Email Project: Login & Registration System Project: CRUD App with MySQL Project: Shopping Cart with Sessions Project: REST API with PHP DateTime Class Timezones PHP Include Include vs Require PHP File Handling PHP File Open/Read PHP File Create/Write File Delete / Rename / Copy PHP Directories PHP CSV Files
Ask about this post
AI Ask about this post

Ask questions about Nested Loops and get answers drawn from it.

Signed-in readers only.