Grade by Grade
A. Programming Concepts and Skills · Programming Basics: Data, Decisions and Loops

Nested and Compound Selection

A2.2 A2.3 A1.4 A4.4About 70 minutes
Learning goals
  • I can write and trace an if statement nested inside another if.
  • I can decide when to nest and when to combine conditions with and or or.
  • I can use selection to protect a calculation from bad input, such as dividing by zero.
  • I can draw a decision tree and turn it into code.

Words to know

nested selection
An if statement placed inside a block of another if, elif or else.
compound condition
A condition made from smaller ones with and, or and not.
decision tree
A branching diagram of questions and outcomes, used to plan nested selection.
guard
A condition that checks a value is safe before it is used, such as if den != 0: before dividing.
run-time error
An error that happens while the program runs, such as ZeroDivisionError. Lines before it have already run.

Questions inside questions

A movie theatre's prices: children under 13 pay $8, seniors (65 or over) pay $9, and everyone else pays $14, except on Tuesdays, when they pay $10. To find a price, you first ask about age, and only for one age group do you ask about the day. A question asked only after another question is answered is nested selection.

Expectation A2.3 asks for algorithms with nested structures. In this lesson you nest selection inside selection; in Lesson 13 you will nest selection inside loops.

Writing a nested if

An if inside a block is indented one more level (4 more spaces). Its whole if/elif/else belongs to the outer block.

  • The outer decision runs first.
  • The inner decision runs only when Python enters that outer block.

Indentation shows exactly which else belongs to which if, so line it up carefully.

age?under 1313 to 6465 or over$8$9Tuesday?yesno$10$14
A decision tree for the movie prices in Example 1. Only the 13-to-64 branch asks a second question, so that is where the nested if goes.
Worked example

Example 1: Movie prices

Write code that sets price from age and a Boolean is_tuesday, using the theatre's rules.

  1. Draw the decision tree (above). The first question is age, with three branches.
  2. Only the middle branch (13 to 64) has a second question, so put the nested if there.
  3. Code: - if age < 13: -     price = 8 - elif age >= 65: -     price = 9 - else: -     if is_tuesday: -         price = 10 -     else: -         price = 14
  4. Trace: age 30, Tuesday: not < 13, not ≥ 65, so the else; inside, is_tuesday is True → $10.
  5. Trace: age 70, Tuesday: age >= 65 → $9. The inner question is never asked.

Children $8, seniors $9, others $10 on Tuesday and $14 otherwise.

Nest, or combine with and?

The nested else in Example 1 could also be written flat:

  • elif is_tuesday: → price = 10
  • else: → price = 14

Because the chain only reaches that elif when the age is 13 to 64, it means the same thing. In general:

  • if A: with if B: nested directly inside (and nothing else) is the same as if A and B:. Prefer the flat version: less indentation, easier to read.
  • Nest when the outer branch does something of its own, or when the inner question only makes sense, or is only safe, after the outer one is True.

Guards: protecting your program

Some operations crash with certain values. Dividing by zero raises a ZeroDivisionError, a run-time error: the program stops in the middle. An if can guard the dangerous step so it runs only with safe values. This is the most important use of nested selection.

den != 0 ?Truenested selectionnum % den == 0 ?True"divides evenly"False"has a remainder"False"cannot divide by 0"
A decision inside another decision. The inner question (is there a remainder?) is only asked when the outer one is True, so the program never divides by zero.
num = int(input("Numerator: "))
den = int(input("Denominator: "))
if den != 0:
if num % den == 0:
print(f"{num} / {den} divides evenly: {num // den}")
else:
print(f"{num} / {den} has a remainder of {num % den}")
else:
print("Cannot divide by 0.")

With a denominator of 0, the guard sends the program to the outer else and it prints Cannot divide by 0. instead of crashing. Output, line by line: Numerator: 17 | Denominator: 5 | 17 / 5 has a remainder of 2 (the typed input is shown after each prompt)

Worked example

Example 2: Trace a nested structure

Trace this code for x = 4, y = -2 and for x = -1, y = 5.

  • if x > 0:
  •     if y > 0:
  •         q = "I"
  •     else:
  •         q = "IV"
  • else:
  •     if y > 0:
  •         q = "II"
  •     else:
  •         q = "III"
  • print(q)
  1. It reports the quadrant of the point (x, y) on a coordinate grid (points on an axis go to the else branches; a real program would check them first).
  2. x = 4, y = −2: outer x > 0 is True, so enter the first block. Inner y > 0 is False, so the inner else: q = "IV".
  3. x = −1, y = 5: outer is False, so enter the outer else. Inner y > 0 is True: q = "II".

IV for (4, −2) and II for (−1, 5).

Just for fun

If it is snowing, and if it is a school day, and if the buses are cancelled, and if your homework is done, then you get to sleep in.

Programmers call that a nested if. Everyone else calls it "very unlikely".

Hands-on activity

Decision tree for a board game or sport

You need: paper, a pencil, and Python. Choose a rule set you know: who goes first in a board game, which hockey age division a player is in, or how a curling end is scored.

  1. Draw a decision tree with at least two levels of questions. Label every branch.
  2. Decide which questions are independent (use and/or or separate ifs) and which depend on an earlier answer (nest them).
  3. Write the program with input() for each piece of information. Use a guard for any input that could break a calculation.
  4. Make a test table: one row for every leaf (end point) of your tree, with the inputs and the expected output.
  5. Run every test. Ask a family member to try to "break" your program with odd inputs.

What to look for: the number of test rows equals the number of leaves in the tree; every else lines up under its own if; there is no nested if that could be replaced by a simple and.

Your turn

1

What is printed for a = 5, b = 3? For a = 5, b = 8? For a = -1, b = 8?

  • if a > 0:
  •     if b > a:
  •         print("X")
  •     else:
  •         print("Y")
  • else:
  •     print("Z")
2

Rewrite without nesting:

  • if temp < 0:
  •     if wind > 30:
  •         print("Stay inside")
3

Write a guard so that average = total / count only runs when count is not 0; otherwise print No data.

4

Using Example 1's rules, what is the price for (a) age 12 on a Tuesday, (b) age 13 on a Friday, (c) age 64 on a Tuesday?

5

Why does if count != 0 and total / count > 50: not crash when count is 0?

6

Write nested selection that reads a mark from 0 to 100. If it is outside that range print Invalid; otherwise print Pass (50 or more) or Not yet.

More practice: warm-up

1

What is nested selection?

2

How do you show that an if is inside another if?

3

What error does 5 / 0 cause?

4

Name the condition that checks a value before a risky operation.

5

if A: with if B: nested directly inside and nothing else: what single condition is the same?

More practice: on your own

1

Find the bug: the program should print Freezing rain only when it is precipitating and the temperature is from −3 to 0 (inclusive).

  • if precip:
  •     if -3 <= temp <= 0:
  •         print("Freezing rain")
  • else:
  •     print("Snow")
2

A curling club: members pay $25 per game; non-members pay $35, or $30 if they are students. Write the selection with variables member and student (both Booleans).

3

Trace for n = 12:

  • if n % 2 == 0:
  •     if n % 3 == 0:
  •         print("six")
  •     else:
  •         print("two")
  • else:
  •     print("odd")
4

How many test cases are needed to make every print in the previous question run at least once? Give one value for each.

5

Write a program that reads two numbers and an operator (+, -, * or /) and prints the result, guarding against division by zero and unknown operators.

Challenge

1

Write a program that reads the coefficients of a linear equation ax + b = 0 and prints the solution, handling a = 0 correctly (no solution, or every x is a solution).

2

Rock, paper, scissors: write a condition (one expression with and/or) that is True when player 1 wins, using strings p1 and p2.

3

Explain why this structure can never print C, and suggest what the author probably meant:

  • if x > 10:
  •     print("A")
  • elif x > 20:
  •     print("B")
  •     if x < 5:
  •         print("C")
Check your understanding

Quiz

1.

Which is the same as if a: with if b: nested directly inside it (and nothing else)?

2.

For x = 3: if x > 0: → (if x > 5: → print("big"), else: → print("small")), else: → print("negative"). What is printed?

3.

What is a guard?

4.

What decides which if an else belongs to in Python?

5.

A decision tree has 5 leaves (end points). What is the minimum number of tests to run every branch?

6.

Which is the best reason to nest instead of using and?

For parents and teachers

Big idea: expectation A2.2 (selection) extended to nested structures (A2.3), using compound Boolean conditions (A1.4) and tracing (A4.4).

How to help: ask your teen to explain a decision tree for something familiar (a sports league's age divisions, a bus fare chart) and count its leaves. Then ask how many tests are needed.

Common difficulties:

  • An else at the wrong indentation, so it belongs to the wrong if.
  • Deep nesting where a simple and would do.
  • Forgetting a guard, so the program crashes on 0 or on an empty value.
  • Branches that can never be reached because an earlier condition already caught those values.