Module 2: Decisions and Validation
Branching That Stays Readable
How if, elif, and else choose exactly one path, why branch order is part of the rule, and how to flatten nesting before it becomes unreadable.
Lesson 8 of 46 in the recommended order · About 25 min (estimate)
On this page
Outcome
By the end of this lesson you can read an if/elif/else chain and name the one branch that runs for any given record, and you can flatten a nested version into a chain without changing what it does.
Why it matters
Branching is where a program's policy lives. A reviewer asking "why was this opportunity marked low priority?" is really asking which branch ran, and a chain that a person cannot follow is a policy nobody can audit.
Generated code has a particular failure mode here: it tends to nest. Each new requirement becomes another level of indentation, and after four of them nobody, including the author, can say which combinations reach the bottom.
Concept
if runs its indented block when its condition is true. elif offers another condition, checked only if every condition above it was false. else runs when none matched. At most one branch in a chain ever runs, and the chain stops at the first match.
That makes branch order part of the rule, not a style choice. Put the most specific condition first. A broad condition placed early makes every narrower one below it unreachable, and Python will not warn you, because an unreachable branch is not an error.
Indentation is the syntax. The lines inside a branch are indented consistently, by convention four spaces, and the indentation is what tells Python where the block ends. There are no braces to disagree with the layout, which means the code cannot lie about its own structure.
Nesting is legal and usually avoidable. A nested chain like "if A: if B: X else: Y" often flattens to "if A and B: X elif A: Y", which is one level shallower and states each outcome's full condition on one line. Flattening is not always better, but when the nesting exists only to combine conditions, it almost always is.
Read the code
Two versions of the same rule. First, nested:
status = "Active"
estimated_value = 180000
if status == "Active":
if estimated_value >= 250000:
band = "Standard"
else:
band = "Backlog"
else:
band = "Closed"
print(band)
Now flat:
status = "Active"
estimated_value = 180000
if status != "Active":
band = "Closed"
elif estimated_value >= 250000:
band = "Standard"
else:
band = "Backlog"
print(band)
Both produce the same band for every possible pair of inputs. The second version reads top to bottom as three complete sentences: not active means closed; otherwise large means standard; otherwise backlog. The first requires holding status in your head while you read the inner block.
Notice that flattening reversed the first condition, from == "Active" to != "Active". Handling the exceptional case first and getting it out of the way is what removes the nesting.
Compare the two decision paths
Same three outcomes, two decision paths
Choose one version: Start at N1 for nested code or F1 for flat code.
N and F label alternative programs. They are not four checks in a single run. An assigned band ends that path.
N1 · Nested: outer condition
Test- status == "Active"
True →- Continue → N2
False →- band = "Closed" → stop
Only Active records enter the inner condition.
F1 · Flat: first condition
Test- status != "Active"
True →- band = "Closed" → stop
False →- Continue → F2
The elif condition is checked only when this first condition is false.
N2 · Nested: inner condition
Test- estimated_value >= 250000
True →- band = "Standard" → stop
False →- band = "Backlog" → stop
Reached only through N1 true. Status is already known to be Active.
F2 · Flat: elif condition
Test- estimated_value >= 250000
True →- band = "Standard" → stop
False →- else: band = "Backlog" → stop
Reached only through F1 false. Status is already known to be Active.
Predict the output
Both programs print one word. Predict it, then predict what each prints if estimated_value is changed to 250000.
Check your prediction
Both print Backlog, because 180000 >= 250000 is false.
With estimated_value = 250000, both print Standard. The comparison is >=, so the boundary value itself qualifies. If it had been written >, the same record would print Backlog, and nothing about the code would look wrong. Boundary values deserve a deliberate decision and a written note, every time.
Modify the code
In the flat version, move the elif estimated_value >= 250000: branch above the if status != "Active": branch, making it the new if. Predict what a record with status = "Closed" and estimated_value = 400000 now produces.
What changes, and why
It produces Standard, for a closed opportunity. The reordered chain checks the value first, matches, and never looks at the status at all.
No error, no warning, no visible symptom until someone notices a closed notice in the review queue. This is the clearest reason branch order is part of the rule: moving two lines changed the policy without changing a single condition.
Debug the bug
An assistant was asked for a three-band classifier. It produced this, and said high-value records are marked Standard.
estimated_value = 400000
days_until_close = 20
if estimated_value >= 0:
band = "Backlog"
elif estimated_value >= 250000:
band = "Standard"
elif days_until_close <= 7:
band = "Urgent"
print(band)
What's actually wrong
For every non-negative amount, this code prints Backlog: the first condition, estimated_value >= 0, matches and the chain stops, regardless of the closing window. The Standard branch is unreachable because any amount at least 250000 already matched the first condition. The Urgent branch can run when the amount is negative and days_until_close <= 7; the code has not rejected negative amounts.
There is a second defect hiding behind the first. There is no else, so if a record ever did reach the bottom with nothing matching, band would never be assigned and the print would raise NameError: name 'band' is not defined. A chain that assigns a value should end with an else, so that every path produces one.
Both faults share a cause: the chain was written as three independent thoughts rather than one ordered decision. Reading it aloud in order, "if the value is at least zero", catches it in seconds.
Try it yourself
Classify one opportunity into exactly one priority band. The record sits precisely on the urgency boundary, so your comparison has to be deliberate about whether the boundary counts.
Loading this exercise…
Practical challenge (optional)
Optional: add a fourth band. An opportunity whose status is not "Active" should be classified "Closed" no matter what its value or closing window says. Decide where in the chain it belongs, then write one sentence explaining why it belongs there and not somewhere else. If you can defend the position, you understand branch ordering; if you cannot, the chain is telling you the rule is not yet clear.
Sign in to track your progress on this exercise.
AI collaboration
Checkpoint
Read this chain and answer the three questions below it.
if score > 90:
grade = "high"
elif score > 70:
grade = "medium"
elif score > 95:
grade = "exceptional"
- What does a
scoreof98produce? - Can
gradeever be"exceptional"? - What happens when
scoreis50?
Answers
"high". The first condition matches and the chain stops.- No. Any score above 95 is also above 90, so the first branch always claims it. That branch is unreachable, and Python reports nothing.
- No branch matches and there is no
else, sogradeis never assigned. The next line that reads it raises aNameError.
Sign in to track your progress on this exercise.
Summary and next step
One branch runs at most, the first match wins, branch order encodes policy, an over-broad condition can silently orphan everything beneath it, and a chain that assigns should end with else. Next: making a matching decision explain itself, so the verdict arrives with its reason attached.