Python Basics — Nested Conditions

2 minute read

Nested Conditions

Nesting means placing one if statement inside another. It lets you check conditions step by step — like a decision tree.

Basic Nesting

age = 25
has_id = True

if age >= 18:
    if has_id:
        print("Entry allowed.")
    else:
        print("Please bring your ID.")
else:
    print("Entry denied — must be 18+.")

Output:

Entry allowed.

Each level of nesting requires an additional 4-space indent.

When Nesting Is Appropriate

Nesting works well when decisions depend on each other:

is_member = True
cart_total = 150

if is_member:
    if cart_total >= 100:
        discount = 0.20
        print(f"Member discount: {discount:.0%}")
    else:
        discount = 0.10
        print(f"Member discount: {discount:.0%}")
else:
    if cart_total >= 200:
        discount = 0.05
        print(f"Guest discount: {discount:.0%}")
    else:
        discount = 0
        print("No discount available.")

Output:

Member discount: 20%

The Problem with Deep Nesting

Too much nesting hurts readability:

# Bad — deeply nested and hard to follow
def process_order(user, order):
    if user is not None:
        if user.is_active:
            if order is not None:
                if order.total > 0:
                    if user.balance >= order.total:
                        print("Order processed!")
                    else:
                        print("Insufficient balance.")
                else:
                    print("Empty order.")
            else:
                print("No order provided.")
        else:
            print("Account inactive.")
    else:
        print("No user provided.")

This code forms an “arrow shape” — a sign of trouble.

Flattening Nested Conditions

Strategy 1: Combine with and

# Nested
if age >= 18:
    if has_id:
        print("Entry allowed.")

# Flattened — same result
if age >= 18 and has_id:
    print("Entry allowed.")

Strategy 2: Guard Clauses (Early Return)

Instead of nesting deeper, return early for invalid cases:

# Flattened with guard clauses
def process_order(user, order):
    if user is None:
        print("No user provided.")
        return

    if not user.is_active:
        print("Account inactive.")
        return

    if order is None:
        print("No order provided.")
        return

    if order.total <= 0:
        print("Empty order.")
        return

    if user.balance < order.total:
        print("Insufficient balance.")
        return

    print("Order processed!")

This reads from top to bottom — each guard clause handles one failure case. By the time you reach the last line, all conditions are guaranteed to be met.

Strategy 3: Extract into Functions

def is_eligible_for_discount(member, total):
    return member and total >= 100

def calculate_discount(member, total):
    if is_eligible_for_discount(member, total):
        return 0.20
    elif member:
        return 0.10
    elif total >= 200:
        return 0.05
    return 0

discount = calculate_discount(True, 150)
print(f"Discount: {discount:.0%}")

Output:

Discount: 20%

Readability Guideline

A useful rule of thumb: if your code indents more than 3 levels, refactor.

Nesting Level Verdict
1 level Fine
2 levels Usually fine
3 levels Consider refactoring
4+ levels Definitely refactor

Practical Example: ATM Withdrawal

def withdraw(balance, amount, pin_entered, daily_limit_remaining):
    # Guard clauses — handle failures first
    if not pin_entered:
        return "Error: PIN not verified."

    if amount <= 0:
        return "Error: Invalid amount."

    if amount > daily_limit_remaining:
        return f"Error: Exceeds daily limit (${daily_limit_remaining} remaining)."

    if amount > balance:
        return "Error: Insufficient funds."

    # Happy path — all checks passed
    new_balance = balance - amount
    return f"Dispensing ${amount}. New balance: ${new_balance}"

# Test
print(withdraw(1000, 200, True, 500))
print(withdraw(1000, 200, False, 500))
print(withdraw(100, 200, True, 500))

Output:

Dispensing $200. New balance: $800
Error: PIN not verified.
Error: Insufficient funds.

Summary

  • Nesting places if blocks inside other if blocks
  • It’s useful when decisions genuinely depend on each other
  • Deep nesting (3+ levels) hurts readability
  • Flatten with and/or, guard clauses, or helper functions
  • Guard clauses (early returns) are the most common refactoring pattern

Next up: Ternary Operator →

Updated: