PHP Nested if

Su Subhalakshmi Chandran Updated 17 Oct 2026
8 min read ·Lesson 43 of 102
In this post

What Is a Nested if ? In simple terms: A nested if is an if statement placed inside another if (or else ) block. The inner condition is checked only when the outer condition has already been decided.

What Is a Nested if?

In simple terms: A nested if is an if statement placed inside another if (or else) block. The inner condition is checked only when the outer condition has already been decided.

Technically: Any statement can appear inside an if block, including another if. Nesting creates a hierarchy of decisions: the inner decision depends on the outcome of the outer one.

Why Nesting Exists

Some decisions only make sense after an earlier decision:

  • Check the PIN only if the card is valid.
  • Check the user's role only if they are logged in.
  • Apply domestic shipping rates only if the address is in the country.

Nesting expresses this dependency directly. However, deep nesting quickly becomes hard to read, so this lesson also covers how to flatten nested code when it grows.


Syntax

if (outerCondition) {
    // runs when outerCondition is true

    if (innerCondition) {
        // runs when both outerCondition and innerCondition are true
    } else {
        // runs when outerCondition is true but innerCondition is false
    }
} else {
    // runs when outerCondition is false
}

Example: Event Entry

<?php
function entryMessage(bool $hasTicket, int $age): string
{
    if ($hasTicket) {
        if ($age >= 18) {
            return "Welcome! Enjoy the event.";
        } else {
            return "Ticket accepted. Please collect a minor's wristband.";
        }
    } else {
        return "Please buy a ticket at the counter.";
    }
}

echo entryMessage(true, 25) . "\n";
echo entryMessage(true, 15) . "\n";
echo entryMessage(false, 30) . "\n";

Output

Welcome! Enjoy the event.
Ticket accepted. Please collect a minor's wristband.
Please buy a ticket at the counter.

How It Works

  1. The outer if checks whether the visitor has a ticket.
  2. Only for ticket holders does the inner if check the age, which leads to two different messages.
  3. Visitors without a ticket go to the outer else; their age is never checked, because it does not matter for them.

This structure shows the dependency clearly: age matters only for ticket holders.


Nested if vs Combined Conditions

When the inner if has no else, and the outer block contains nothing else, the two conditions can usually be combined with &&.

Nested

<?php
$isLoggedIn = true;
$isAdmin = true;

if ($isLoggedIn) {
    if ($isAdmin) {
        echo "Show admin menu\n";
    }
}

Combined

<?php
$isLoggedIn = true;
$isAdmin = true;

if ($isLoggedIn && $isAdmin) {
    echo "Show admin menu\n";
}

Output (both)

Show admin menu

The combined version is shorter and flatter. Because && short-circuits, $isAdmin is still checked only when $isLoggedIn is true, so the dependency is preserved.

When Nesting Is the Better Choice

Keep the nesting when the outer block does more than one thing or each level needs its own else:

<?php
$isLoggedIn = true;
$isAdmin = false;

if ($isLoggedIn) {
    echo "Show account menu\n";

    if ($isAdmin) {
        echo "Show admin menu\n";
    }
} else {
    echo "Show login button\n";
}

Output

Show account menu

Here the outer block always shows the account menu for logged-in users, and the inner if adds the admin menu only for administrators. Combining the conditions would require repeating $isLoggedIn in several places.


The Problem With Deep Nesting

Each level of nesting adds indentation and another condition the reader must keep in mind. Consider an ATM withdrawal:

<?php
function withdraw(bool $cardValid, bool $pinCorrect, float $balance, float $amount): string
{
    if ($cardValid) {
        if ($pinCorrect) {
            if ($amount <= $balance) {
                return "Dispensing ₹" . number_format($amount, 2);
            } else {
                return "Insufficient balance";
            }
        } else {
            return "Incorrect PIN";
        }
    } else {
        return "Card not accepted";
    }
}

echo withdraw(true, true, 5000, 2000) . "\n";
echo withdraw(true, true, 1000, 2000) . "\n";
echo withdraw(true, false, 5000, 2000) . "\n";
echo withdraw(false, true, 5000, 2000) . "\n";

Output

Dispensing ₹2,000.00
Insufficient balance
Incorrect PIN
Card not accepted

The code is correct, but:

  • The successful path — the one that matters most — is buried three levels deep.
  • Each else is far away from the condition it belongs to. To find which condition leads to "Card not accepted", you must match braces across the whole function.
  • Adding another check (for example, a daily withdrawal limit) means another level of nesting.

This shape is sometimes called the arrow anti-pattern, because the indented code forms an arrow pointing to the right.


Flattening With Guard Clauses

A guard clause handles a failure case first and returns immediately. Each check then sits at the top level of the function.

<?php
function withdraw(bool $cardValid, bool $pinCorrect, float $balance, float $amount): string
{
    if (!$cardValid) {
        return "Card not accepted";
    }

    if (!$pinCorrect) {
        return "Incorrect PIN";
    }

    if ($amount > $balance) {
        return "Insufficient balance";
    }

    return "Dispensing ₹" . number_format($amount, 2);
}

echo withdraw(true, true, 5000, 2000) . "\n";
echo withdraw(true, true, 1000, 2000) . "\n";
echo withdraw(true, false, 5000, 2000) . "\n";
echo withdraw(false, true, 5000, 2000) . "\n";

Output

Dispensing ₹2,000.00
Insufficient balance
Incorrect PIN
Card not accepted

Why This Is Better

  1. Each condition is immediately followed by its consequence.
  2. The checks run in the same order as before, so the behavior is identical.
  3. The successful path is the last line, at the lowest indentation, easy to find.
  4. Adding a new check means adding one more guard — no re-indentation of existing code.

Guard clauses work best inside functions, where return can end processing. In loops, continue plays the same role (see Continue Statement).


Flattening With match (true)

When nested if statements only choose a value, a match (true) expression can replace the whole structure.

Nested Version

<?php
function shippingFee(string $country, float $weightKg, float $cartTotal): int
{
    if ($country === 'IN') {
        if ($weightKg <= 5) {
            if ($cartTotal >= 999) {
                return 0;
            } else {
                return 79;
            }
        } else {
            return 149;
        }
    } else {
        if ($weightKg <= 5) {
            return 999;
        } else {
            return 1999;
        }
    }
}

Flat Version

<?php
function shippingFee(string $country, float $weightKg, float $cartTotal): int
{
    return match (true) {
        $country !== 'IN' => $weightKg <= 5 ? 999 : 1999,
        $weightKg > 5 => 149,
        $cartTotal >= 999 => 0,
        default => 79,
    };
}

$tests = [
    ['IN', 2, 1200],
    ['IN', 2, 500],
    ['IN', 8, 1200],
    ['US', 2, 500],
    ['US', 8, 500],
];

foreach ($tests as [$country, $weight, $total]) {
    echo "$country, {$weight} kg, ₹$total → ₹" . shippingFee($country, $weight, $total) . "\n";
}

Output

IN, 2 kg, ₹1200 → ₹0
IN, 2 kg, ₹500 → ₹79
IN, 8 kg, ₹1200 → ₹149
US, 2 kg, ₹500 → ₹999
US, 8 kg, ₹500 → ₹1999

How the Flat Version Works

match (true) checks each condition from top to bottom and returns the value of the first one that is true:

  1. International orders are handled first, with a small ternary for the weight.
  2. For domestic orders, heavy parcels cost ₹149, whatever the cart total.
  3. Light domestic parcels are free above ₹999.
  4. Everything else costs ₹79.

The order of the arms encodes the same logic as the nesting, but the full rule table is visible at a glance. match is covered in PHP Match.


Nesting in Loops and Templates

Nested if statements also appear inside loops and HTML templates. The same advice applies: keep nesting shallow, and in loops, use continue to skip items early.

<?php
$orders = [
    ['id' => 1, 'paid' => true, 'items' => 3],
    ['id' => 2, 'paid' => false, 'items' => 1],
    ['id' => 3, 'paid' => true, 'items' => 0],
];

foreach ($orders as $order) {
    if (!$order['paid']) {
        echo "Order {$order['id']}: waiting for payment\n";
        continue;
    }

    if ($order['items'] === 0) {
        echo "Order {$order['id']}: empty order, skipped\n";
        continue;
    }

    echo "Order {$order['id']}: ready to pack {$order['items']} items\n";
}

Output

Order 1: ready to pack 3 items
Order 2: waiting for payment
Order 3: empty order, skipped

Each special case is handled and skipped with continue, so the main processing line is not nested at all.


Common Mistakes

Mismatched else Without Braces

Effect: An else belongs to the nearest if, which may not be the one its indentation suggests (the "dangling else").

Fix: Always use braces at every level.

Deep Nesting

Effect: Code that is hard to read, review, and extend.

Fix: Use guard clauses, combine conditions with &&, or use match (true). As a rule of thumb, more than two or three levels is a signal to refactor.

Repeating the Same Check at Several Levels

Effect: Duplicated conditions that can drift apart when one copy is changed.

Fix: Check each condition once, and store results in named variables if they are reused.

Nesting Independent Checks

Effect: A check is skipped because it was placed inside an unrelated condition.

Fix: Nest only when the inner check truly depends on the outer one; otherwise, use separate if statements.


Best Practices

  • Nest only when an inner decision depends on an outer one and each level has its own meaningful outcome.
  • Combine simple dependent conditions with && when there is no else at the inner level.
  • In functions, prefer guard clauses that return early; in loops, use continue.
  • Use match (true) when nested conditions only select a value.
  • Always use braces, and keep nesting to two or three levels at most.

Official reference: https://www.php.net/manual/en/control-structures.if.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 Home 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 Slicing Strings User-defined Functions Slicing Strings 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
Ask about this post
AI Ask about this post

Ask questions about PHP Nested if and get answers drawn from it.

Signed-in readers only.