043. Nested Conditionals
Avoid deep nesting with early returns
043. Nested Conditionals
RAM Manager: Kill Eligibility
Aryan’s RAM manager needs to decide whether a process can be killed:
- Must not be a system process (pid > 100)
- Must be using more than 10% RAM
- Must not be in the protected list
His first version is a “pyramid of doom” — deeply nested if/else that’s hard to read and easy to get wrong:
# Pyramid of doom version
def can_kill(pid, mem_pct, protected):
if pid > 100:
if mem_pct > 10:
if pid not in protected:
return True
else:
return False
else:
return False
else:
return FalseThe guard clause refactoring is not just a style preference — it prevents bugs. When you add a 4th condition to the pyramid, you have to find the right nesting level. With guard clauses, you just add another if not ...: return False line.
💡 Fun fact: The “pyramid of doom” or “arrow anti-pattern” — deeply nested conditionals that push code to the right — is so notorious that Jeff Atwood coined the term on his blog Coding Horror in 2006. The problem exists in every language, but it’s especially visible in Python where indentation is syntactically enforced, making the nesting physically painful to read.
⚠️ Watch out: When inverting conditions for guard clauses, beginners often flip the logic incorrectly. if age >= 18: inverts to if age < 18: return False — not if age <= 18:. An off-by-one in the guard condition silently excludes valid inputs or accepts invalid ones at the boundary value.
🤔 Think about it: The guard clause pattern returns False early for each failing condition, then returns True at the bottom. What if you have a function with 10 guard clauses — is that a sign the function is well-structured, or a code smell suggesting it should be broken into smaller functions?
Learning objectives
- Recognize and refactor deeply nested conditionals
- Use early returns (guard clauses) to flatten nesting
- Replace nested if/else with if/elif chains
Key concepts
- nested conditionals
- guard clauses
- early return
- flat structure
Try it
Concept detail
The “pyramid of doom” — nested conditionals — makes code hard to read and extend:
if cond1: if cond2: if cond3: return success # buried at level 3 else: return fail3 # where does this case end up? else: return fail2 else: return fail1
Guard clause refactoring — invert each failing condition, return early, flatten:
if not cond1: return fail1 # guard 1 if not cond2: return fail2 # guard 2 if not cond3: return fail3 # guard 3 return success # happy path — always last, always visible
Benefits:
- Each condition is one line, easy to scan
- Adding a 4th condition is trivial (add one line)
- The success case is obvious (it’s the last line)
- Easier to unit test each guard independently
For mutually exclusive classification (not guards), use if/elif/else, not nested else-if. Nested else-if and elif chains are functionally identical, but elif is one indentation level.
Solution
def is_eligible(age, income, credit_score):
if age < 18:
return False
if income < 30000:
return False
if credit_score < 650:
return False
return True
def classify_loan(amount):
if amount < 1000:
return "micro"
elif amount < 10000:
return "personal"
elif amount < 100000:
return "business"
else:
return "enterprise"Tests
def test_eligible_all_good():
assert is_eligible(25, 50000, 700) == True
def test_too_young():
assert is_eligible(17, 50000, 700) == False
def test_low_income():
assert is_eligible(25, 25000, 700) == False
def test_low_credit():
assert is_eligible(25, 50000, 600) == False
def test_exactly_eligible():
# Boundary values — exactly at minimums
assert is_eligible(18, 30000, 650) == True
def test_micro_loan():
assert classify_loan(500) == "micro"
assert classify_loan(999) == "micro"
def test_personal_loan():
assert classify_loan(1000) == "personal"
assert classify_loan(5000) == "personal"
def test_business_loan():
assert classify_loan(10000) == "business"
assert classify_loan(50000) == "business"
def test_enterprise_loan():
assert classify_loan(100000) == "enterprise"
assert classify_loan(200000) == "enterprise"