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 pathThe 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_balanceTests
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