1.2.16 · D4Introduction to Programming (Python)

Exercises — Nested conditionals

3,176 words14 min readBack to topic

This page is a companion to Nested Conditionals. If any word here feels unfamiliar, the linked notes at the bottom build it from scratch.


How to read a nested if by hand

Before the ladder, one tool you will use in every solution: the indentation ruler.

Figure — Nested conditionals

L1 — Recognition

Exercise 1.1 (L1)

Which of these snippets contains a nested conditional (an if/elif/else inside the body of another)?

# (A)
if a > 0:
    print("pos")
elif a < 0:
    print("neg")
 
# (B)
if a > 0:
    if b > 0:
        print("both pos")
 
# (C)
if a > 0 and b > 0:
    print("both pos")
Recall Solution 1.1

Answer: (B).

  • (A) is if/elif — these are siblings at the same indentation lane, not one inside the other. No nesting.
  • (B) has if b > 0: sitting indented inside the body of if a > 0:. That inner if only runs when the outer is True. This is the nest.
  • (C) uses and to combine two conditions on one line. It behaves like (B) for a single outcome, but structurally it is a flat single if — no inner if. This is the "flatten with and" pattern from the parent note.

Exercise 1.2 (L1)

In the code below, which if does the else belong to?

if temp > 30:
    if humid > 70:
        print("muggy")
    else:
        print("hot and dry")
Recall Solution 1.2

The else sits in the same lane (one indent deep) as if humid > 70:. So it pairs with if humid > 70. It is the inner else. Meaning: we only reach it after temp > 30 was already True, and then found humid > 70 was False → prints hot and dry.


L2 — Application

Exercise 2.1 (L2)

Predict the output.

score = 85
if score >= 60:
    if score >= 90:
        print("A")
    else:
        print("Pass")
else:
    print("Fail")
Recall Solution 2.1
  • score >= 60? 85 >= 60True. Enter outer body; outer else is skipped.
  • score >= 90? 85 >= 90False → take inner else.
  • Output: Pass

Exercise 2.2 (L2)

Predict the output for each value of n: n = 0, n = 7, n = -3.

if n >= 0:
    if n == 0:
        print("zero")
    else:
        print("positive")
else:
    print("negative")
Recall Solution 2.2

Walk all three — covering every case the code can hit:

  • n = 0: 0 >= 0 True → 0 == 0 True → zero.
  • n = 7: 7 >= 0 True → 7 == 0 False → inner elsepositive.
  • n = -3: -3 >= 0 False → outer elsenegative.

Notice: the outer gate splits sign; the inner gate only distinguishes zero-vs-positive inside the non-negative branch. The negative branch never asks the == 0 question — it can't be zero there.

Exercise 2.3 (L2)

Predict the output.

logged_in = True
is_admin  = True
banned    = True
if logged_in:
    if banned:
        print("Access denied")
    else:
        if is_admin:
            print("Admin panel")
        else:
            print("User home")
else:
    print("Login required")
Recall Solution 2.3
  • logged_in True → enter.
  • banned True → Access denied, and we stop. The is_admin question is never asked — banned overrides everything inside this branch.
  • Output: Access denied

L3 — Analysis (find & fix the bug)

Exercise 3.1 (L3)

The intended behaviour: print both positive only when both are positive; otherwise print a not positive only when a <= 0. This code is wrong. Explain why and fix it.

if a > 0:
    if b > 0:
        print("both positive")
else:
    print("a not positive")
Recall Solution 3.1

This code is actually correct as written — the else sits in the same lane as if a > 0, so it pairs with the outer if. Trace it:

  • a > 0 False → outer elsea not positive. ✅
  • a > 0 True, b > 0 True → both positive. ✅
  • a > 0 True, b > 0 False → nothing prints (the inner if has no else), which matches the spec ("only when a<=0" for the message). ✅

The lesson: the lane test confirms correctness. The else is one lane out, so it is the outer else. Don't be spooked by how close the else looks to if b > 0 — measure the lane, not the eyeball distance.

Exercise 3.2 (L3)

This is meant to print both positive for a=1, b=-1... no wait — read the spec: it should print inner else when a > 0 but b <= 0. It doesn't. Fix it so the else attaches to the inner if.

if a > 0:
    if b > 0:
        print("both positive")
else:
    print("inner else")
Recall Solution 3.2

As written, else is on the outer lane, so it fires when a <= 0 — that is the dangling-else trap. To make else pair with if b > 0, push it one lane deeper so it sits in the inner body's lane:

if a > 0:
    if b > 0:
        print("both positive")
    else:
        print("inner else")

Now with a=1, b=-1: a>0 True → b>0 False → inner else. And with a=-5: a>0 False → nothing prints (there is no outer else anymore). Compare the two lane pictures below.

Figure — Nested conditionals

Exercise 3.3 (L3)

Find the runtime/syntax problem and fix it.

x = 5
if x = 5:
    print("five")
Recall Solution 3.3

if x = 5: raises a SyntaxError. = is the assignment operator ("put 5 into x"); a condition needs a comparison. Fix:

if x == 5:
    print("five")

Now x == 5 asks "is x equal to 5?"True → prints five. See Comparison operators.


L4 — Synthesis (build it from words)

Exercise 4.1 (L4)

Write nested conditionals for an ATM:

  • If card_valid is False: print Card rejected.
  • If the card is valid but pin_ok is False: print Wrong PIN.
  • If both are fine but amount > balance: print Insufficient funds.
  • Otherwise: print Dispensing cash.
Recall Solution 4.1

Each check only makes sense after the previous passed — a textbook chain of gates:

if card_valid:
    if pin_ok:
        if amount > balance:
            print("Insufficient funds")
        else:
            print("Dispensing cash")
    else:
        print("Wrong PIN")
else:
    print("Card rejected")

Trace check with card_valid=True, pin_ok=True, amount=100, balance=500: valid → pin ok → 100 > 500? False → elseDispensing cash. ✅

Trace with card_valid=True, pin_ok=False: valid → pin False → Wrong PIN (the amount check is never reached — you can't withdraw with a bad PIN). ✅

Exercise 4.2 (L4)

A number n is given. Print one of four labels: "neg even", "neg odd", "non-neg even", "non-neg odd". Use an outer gate on sign and an inner gate on parity. (Parity: n % 2 == 0 means even.)

Recall Solution 4.2

% is the remainder operator: n % 2 is what's left after dividing by 2, so it's 0 for even and 1 (or -1) for odd. We test == 0.

if n < 0:
    if n % 2 == 0:
        print("neg even")
    else:
        print("neg odd")
else:
    if n % 2 == 0:
        print("non-neg even")
    else:
        print("non-neg odd")

Cover all four buckets with n = -4, -3, 0, 7:

  • -4: <0 True, -4 % 2 == 0 True → neg even.
  • -3: <0 True, -3 % 2 == 0? In Python -3 % 2 == 1, so False → neg odd.
  • 0: <0 False, 0 % 2 == 0 True → non-neg even.
  • 7: <0 False, 7 % 2 == 0 False → non-neg odd. ✅

(Python's % always returns a result with the sign of the divisor 2, so -3 % 2 is 1, not -1. Testing == 0 for evenness is safe in every case.)


L5 — Mastery

Exercise 5.1 (L5) — Prove two versions are equal

Show that this nested version and this flat version produce the same output for every input:

# Nested
if age >= 18:
    if has_license:
        print("Can drive")
 
# Flat
if age >= 18 and has_license:
    print("Can drive")
Recall Solution 5.1

There is exactly one printed outcome (Can drive) and it appears only when both conditions hold. To prove two branchings behave identically we use a truth table: a grid that lists every possible combination of the input conditions and records what each version does. Here each condition is either T (True) or F (False), so with two conditions there are 2 × 2 = 4 rows — every situation the world can present.

Read the table like this: pick a row (a scenario), then follow across to see whether the nested code prints and whether the flat code prints. If those two answers agree in every row, the versions are provably equivalent.

age>=18 has_license Nested prints? and value Flat prints?
T T reaches inner, True → yes T yes
T F reaches inner, False → no F no
F T outer skipped → no F no
F F outer skipped → no F no

The "prints?" columns are identical in all four rows, so the versions are equivalent. Because we produce only one message, flattening with and is the readable choice (see Code readability and refactoring and Boolean logic and operators). Keep nesting only when each level needs its own distinct branch/message.

Exercise 5.2 (L5) — Refactor arrow code

Refactor this deep "arrow code" into shallower logic without changing behaviour. Assume every branch either prints or does nothing.

if a:
    if b:
        if c:
            print("all three")
Recall Solution 5.2

Three nested gates with a single outcome and no else branches → flatten with and:

if a and b and c:
    print("all three")

What "short-circuits" means: short-circuit evaluation is when a Boolean operator stops as soon as the answer is certain, without looking at the remaining operands. For and, Python checks the left operand first; if it is falsy the whole and is already False, so Python returns immediately and never even evaluates what comes after. (Likewise or stops at the first truthy operand — see Boolean logic and operators.)

So a and b and c checks a first; if a is falsy it never evaluates b or c. That means the "don't ask the inner question unless the outer passed" safety of nesting is preservedand gives the same laziness. Depth drops from 3 to 1. Only keep the nest if, say, you needed to print("a failed") at a level — then a branch message forces nesting.

Exercise 5.3 (L5) — Full edge-case sweep

Classify a value x (which may be an int or None) with this spec and write the code, then trace every case:

  • If x is None: "missing".
  • Else if x == 0: "zero".
  • Else if x > 0: "positive".
  • Else: "negative". Use an outer None-gate and a nested numeric decision so we never compare None with >.
Recall Solution 5.3

Comparing None > 0 raises a TypeError in Python 3, so we must gate None out first — this is the "unsafe inner check" reason for nesting from the parent note:

if x is None:
    print("missing")
else:
    if x == 0:
        print("zero")
    elif x > 0:
        print("positive")
    else:
        print("negative")

Sweep every case:

  • x = None: x is None True → missing (numeric block never runs — safe!).
  • x = 0: not None → x == 0 True → zero.
  • x = 42: not None → not 0 → 42 > 0 True → positive.
  • x = -8: not None → not 0 → -8 > 0 False → elsenegative.

The outer gate protects the inner comparisons. No input can reach x > 0 while x is None. Every case is covered and none crashes.


Recap ladder

Recall

The five levels ::: L1 recognise a nest vs an elif chain; L2 trace outputs gate-by-gate over all cases; L3 fix dangling-else via the lane rule; L4 build nested logic from a spec; L5 prove equivalence, refactor arrow code, and gate unsafe operations. The single rule for else pairing ::: it binds to the nearest unmatched if in the same indentation lane. When to flatten with and ::: one single outcome and no per-level messages, and short-circuiting keeps inner checks safe. When to keep the nest ::: distinct messages per level, or an inner check that is unsafe unless the outer passed.

Connections

  • Nested Conditionals — the parent topic these exercises drill.
  • Conditional statements (if-elif-else) — the branch building blocks.
  • Boolean logic and operatorsand/short-circuiting used in L5.
  • Indentation in Python — the lane rule behind every L3 fix.
  • Comparison operators==, >, <, % used throughout.
  • Code readability and refactoring — refactoring arrow code in L5.