← Home

040. If Statement

Conditional branching on boolean expressions

040. If Statement

RAM Manager: Kill Decision Logic

Aryan’s RAM manager needs to decide whether to kill a process. He writes a should_kill(mem_pct, name) function — but first he needs to get guard clauses right.

The classic mistake: checking the wrong condition first, or using the wrong operator.

# What he wants
def withdraw(balance, amount):
    if amount <= 0:         # guard: invalid input
        return "invalid amount"
    if amount > balance:    # guard: not enough funds
        return "insufficient funds"
    return balance - amount  # happy path

The broken code only checks one condition and returns the wrong error string. A realistic bug — the kind that passes quick testing (withdraw(100, 50) works) but fails edge cases (withdraw(100, 0) returns 0 instead of "invalid amount").


💡 Fun fact: The “guard clause” pattern Aryan is learning — checking invalid inputs first and returning early — was popularized by Robert C. Martin in “Clean Code” (2008) as a way to eliminate the “Arrow Anti-Pattern” (deeply nested if/else blocks that look like an arrow pointing right). It’s now a standard practice in professional codebases worldwide.

⚠️ Watch out: The order of guard clauses matters. If you check amount > balance before amount <= 0, then withdraw(100, -50) correctly hits the first guard and returns "insufficient funds" — but that’s the wrong error message for a negative amount. Always validate input integrity (is the value sensible?) before checking business rules (does it fit the current state?).

🤔 Think about it: if amount <= 0 covers both zero and negatives in one check. Could you write this as two separate checks — if amount == 0 and if amount < 0 — and get the same behavior? When would splitting one compound condition into two separate guards make code clearer rather than redundant?

Learning objectives

  • Write basic if statements for conditional execution
  • Use early return (guard clauses) for validation
  • Understand truthy/falsy values

Key concepts

  • if statement
  • conditional branching
  • guard clauses

Try it

Concept detail

if condition: runs the indented block when condition is truthy.

Truthy values: non-zero numbers, non-empty strings/lists/dicts, True, any object Falsy values: 0, 0.0, None, “”, [], {}, set(), False

Guard clause pattern — check invalid states first, return early: def process(x): if x is None: return “error: no value” # guard if x < 0: return “error: negative” # guard return x * 2 # happy path — only reached if all guards pass

Why guard clauses? Without them you get nesting: if x is not None: if x >= 0: return x * 2 # buried 2 levels deep

The guard version is flat, reads top to bottom, and the normal path is always the LAST line.

Comparison operators: == != < > <= >= (same as C) Boolean operators: and or not (unlike C’s && || !) Identity: is None (use this, not == None) Membership: in (if x in my_list:)

Solution

def withdraw(balance, amount):
    if amount <= 0:
        return "invalid amount"
    if amount > balance:
        return "insufficient funds"
    new_balance = balance - amount
    return new_balance

Tests

def test_successful_withdrawal():
    assert withdraw(100, 50) == 50

def test_insufficient_funds():
    assert withdraw(100, 150) == "insufficient funds"

def test_insufficient_funds_not_error():
    result = withdraw(100, 150)
    assert result == "insufficient funds", f"Got {repr(result)} — return 'insufficient funds', not 'error'"

def test_invalid_zero():
    assert withdraw(100, 0) == "invalid amount"

def test_invalid_negative():
    assert withdraw(100, -20) == "invalid amount"

def test_exact_balance():
    assert withdraw(100, 100) == 0

Resources