Python Part I · Module 9 · Conditional Statements
if, elif and else
Every program you have written so far runs every line, top to bottom, exactly once. That is enough to transform data but not enough to react to it. A regression that counts four errors and one fatal needs to say something different from one that counts none, and nothing you have met can express that difference. This chapter adds the one construct that can: a decision. You will see precisely what Python evaluates, how it chooses a block to run, why only one branch of a chain ever executes, and why writing the same two branches in the other order can silently make one of them unreachable.
Foundation16 min readPythonPart IFundamentalsConditionalsControl Flow
Part I · Module 9 · Chapter 9.1 · if, elif and else
1. The Smallest Real Decision
A regression run produces a count. What you want to say depends on the count.
errors = 2
if errors > 0:
print("review the log")
print("analysis complete")review the log
analysis completeNow change one value — nothing else — and run it again:
errors = 0
if errors > 0:
print("review the log")
print("analysis complete")analysis completeThe first line disappeared. The second did not.
That is the whole shape of an if: an indented block that runs only under a condition, and the rest of the program, which runs regardless. The if governs its block. It does not govern everything below it.
2. What Python Actually Evaluates
It is tempting to read if errors > 0: as "if checks whether errors is greater than zero". That describes the effect and hides the mechanism, and the mechanism is what lets you reason about harder conditions later.
Two separate things happen.
First, Python evaluates the expression errors > 0. You already know what that produces on its own — Chapter 3.4 established it:
errors = 2
print(errors > 0)
print(type(errors > 0))True
<class 'bool'>errors > 0 is an ordinary expression. It produces a value, and that value is a bool.
Second, control flow uses that value to choose a path. If the value is true, the indented block executes. If not, the block is skipped entirely.
So the model is:
- evaluate the condition → get a truth value
- select a path from that value
- execute only the selected path
- continue with the rest of the program
Keeping stage 1 and stage 3 apart pays off immediately. A condition is just an expression, so anything that produces a value can be one — and everything you learned about expressions in Module 3 still applies inside an if.
3. else: The Other Path
Often there is something to say in both cases. else provides the path taken when the condition was not true.
status = "PASS"
if status == "PASS":
print("continue to the next stage")
else:
print("investigate before continuing")continue to the next stagestatus = "FAIL"
if status == "PASS":
print("continue to the next stage")
else:
print("investigate before continuing")investigate before continuingExactly one of those two blocks runs. Never both, never neither.
4. More Than Two Outcomes
Two paths are often not enough. A regression result is not pass-or-fail; it is clean, or it has errors, or it has a fatal, and those deserve three different verdicts.
elif adds an alternative with its own condition:
fatals = 0
errors = 4
if fatals:
status = "FATAL"
elif errors:
status = "FAIL"
else:
status = "PASS"
print(status)FAILRead it as one question with three possible answers. fatals is 0, so the first branch is not taken. errors is 4, so the second is. The else is not reached at all.
A chain may have as many elif branches as the problem needs, and the else is optional — leave it out and it is possible for no branch to run at all.
5. The First True Branch Wins
Here is the rule that makes a chain predictable:
Python evaluates the conditions from top to bottom. The first one that is true selects its block. That block runs, and the entire rest of the chain is skipped.
coverage = 93
if coverage >= 95:
print("excellent")
elif coverage >= 90:
print("good")
else:
print("needs work")good93 >= 95 is false, so the first branch is passed over. 93 >= 90 is true, so the second branch runs — and else never gets a chance, even though the chain continues past it on the page.
6. The Rest of the Chain Is Not Even Evaluated
"Skipped" is stronger than it sounds. Python does not evaluate the later conditions and discard the results. It does not evaluate them at all.
That is testable. Put an expression in a later condition that would certainly fail if it were evaluated:
errors = 3
if errors > 0:
status = "FAIL"
elif 1 / 0:
status = "impossible"
print(status)FAIL1 / 0 is a ZeroDivisionError waiting to happen. It never happens. The first branch matched, so Python stopped — it never looked at the second condition, so the division never ran.
7. Branch Order Is Meaning
Because the first true branch wins, the order you write branches in is part of what they mean. Reorder a chain and you can change its answer — or make a branch that can never be selected.
Here is a chain that is broken by design. Read it, predict the output for a score of 97, and then look:
score = 97
if score >= 80:
print("good")
elif score >= 95:
print("excellent")goodA 97 is excellent, and the program said good.
Nothing is wrong with either condition in isolation. The problem is the order. Every score that satisfies score >= 95 also satisfies score >= 80, so the first branch catches it first, and the second branch can never run for any input at all. It is dead code that looks alive.
Written the other way round, both branches become reachable:
score = 97
if score >= 95:
print("excellent")
elif score >= 80:
print("good")excellentFor an ordered chain over one quantity, the working rule is: start at the most selective end. Thresholds that are hardest to satisfy go first.
8. Boundaries Are Where Conditions Go Wrong
Once the order is right, the remaining question is what happens exactly at each threshold. >= includes the boundary value and > does not, and the difference only ever shows up at one input.
Take the corrected chain and test the values on and around its two thresholds:
if 79 >= 95:
print("79: excellent")
elif 79 >= 80:
print("79: good")
else:
print("79: needs work")
if 80 >= 95:
print("80: excellent")
elif 80 >= 80:
print("80: good")
else:
print("80: needs work")
if 95 >= 95:
print("95: excellent")
elif 95 >= 80:
print("95: good")
else:
print("95: needs work")79: needs work
80: good
95: excellent| Input | >= 95 | >= 80 | Selected branch |
|---|---|---|---|
| 79 | False | False | else |
| 80 | False | True | elif |
| 94 | False | True | elif |
| 95 | True | — | if |
| 97 | True | — | if |
The dashes are not omissions. For 95 and 97 the first condition already matched, so >= 80 was never evaluated — Section 6's rule, visible in a table.
Note where the behaviour actually changes: between 79 and 80, and between 94 and 95. Testing 50 and 100 would have told you almost nothing. Test the boundary and its two neighbours, every time.
9. Several Independent ifs Are Not a Chain
This is the misconception worth the most. Two separate if statements and an if/elif chain look similar and behave differently.
Two independent if statements:
value = 12
if value > 5:
print("greater than five")
if value > 10:
print("greater than ten")greater than five
greater than tenBoth ran. They are two unrelated questions, each answered on its own.
The same two conditions as a chain:
value = 12
if value > 5:
print("greater than five")
elif value > 10:
print("greater than ten")greater than fiveOne ran. They are one question with two candidate answers, and the first match ended it.
In fact this particular chain is worse than it looks. Every value above 10 is also above 5, so elif value > 10: is unreachable for every input — it is Section 7's bug, arrived at by converting two good independent checks into a chain rather than by writing the thresholds in the wrong order. Changing the form changed the meaning, and one of the two questions stopped being asked at all.
Same data, same conditions, different output — because the two forms ask different things:
| Form | Question it asks | How many blocks can run |
|---|---|---|
separate if statements | which of these are true? | any number, including all |
if / elif chain | which is the first true one? | at most one |
Choose by meaning, not by habit. A test can be both slow and failing, and you want to hear about both — that is two if statements. A test is clean, failing, or fatal and never two of those at once — that is a chain.
10. Predict, Run, Observe
Work out all four before reading the output.
# A
errors = 0
if errors > 0:
print("A: errors")
print("A: done")
# B
temp = 85
if temp >= 100:
print("B: critical")
elif temp >= 80:
print("B: warm")
elif temp >= 90:
print("B: hot")
else:
print("B: normal")
# C
width = 8
if width > 4:
print("C: wide")
if width > 6:
print("C: wider")
if width > 16:
print("C: widest")
# D
status = "WARN"
if status == "PASS":
print("D: ship")
elif status == "WARN":
print("D: review")
else:
print("D: block")The verified output:
A: done
B: warm
C: wide
C: wider
D: reviewA — the condition is false, so the block is skipped; the unindented line runs anyway.
B — 85 >= 100 is false, 85 >= 80 is true, so B: warm is selected and the chain stops. Notice the third branch: temp >= 90 sits below temp >= 80, so it is unreachable for every input, exactly like Section 7. A temperature of 95 would also print B: warm.
C — three independent statements, two of which are true. Both print.
D — the second branch matches and the else is not reached.
11. Exercises
Exercise 1 — predict the branch. For utilization = 90, say which line prints and why, then verify:
if utilization > 90:
print("over")
elif utilization >= 90:
print("at the limit")
else:
print("under")Exercise 2 — find the unreachable branch. One branch below can never be selected. Name it, explain in one sentence why, and rewrite the chain so every branch is reachable.
if errors >= 1:
print("some errors")
elif errors >= 100:
print("severe")
else:
print("clean")Exercise 3 — chain or separate. For each pair of questions, say whether it should be two if statements or one if/elif chain, in one sentence each: (a) did the test fail, and did it run longer than an hour (b) is the result PASS, FAIL or FATAL (c) is the log missing, and is the log empty (d) is a seed below 1000, between 1000 and 9999, or above.
Exercise 4 — boundary table. For the chain in Exercise 1, build a table like Section 8's for the inputs 89, 90 and 91. Mark which conditions were evaluated and which were never reached.
Exercise 5 — repair the order. This chain is meant to report the most severe condition that applies. Fix it, then verify with fatals = 1, errors = 5 and with fatals = 0, errors = 5.
if errors > 0:
verdict = "FAIL"
elif fatals > 0:
verdict = "FATAL"
else:
verdict = "PASS"Exercise 6 — prove the chain stops. Using the technique from Section 6, write a four-line program that would crash if Python evaluated the second condition of a chain, and show that it does not. Explain what your program proves.
Exercise 7 — design from requirements. A coverage report is signed off at 98 or above, acceptable from 90 to just under 98, at risk from 75 to just under 90, and blocking below 75. Write the chain. Then verify it at 74, 75, 89, 90, 97, 98 and 100 and say why those seven values are the right ones to test.
Exercise 8 — same data, two shapes. Given runtime = 4200 and errors = 0, write the checks for "took over an hour" and "reported no errors" first as two if statements and then as an if/elif chain. Run both, and explain in two sentences which one answers the question a reviewer actually asked.
12. Summary
- An
ifruns its indented block only when a condition is true, and the unindented lines after it run either way. - A condition is an ordinary expression. Python evaluates it first, on its own, producing a value — then control flow uses that value to select a path.
elseis the path taken when nothing above it matched. It has no condition of its own.elifadds an alternative with its own condition. A chain is one question with several candidate answers.- The first true branch wins and the rest of the chain is skipped.
- The rest of the chain is never evaluated —
elif 1 / 0:after a matched branch does not raise, which proves it. - Branch order is part of the meaning. Wider conditions above narrower ones make the narrower ones unreachable, silently.
- Start an ordered chain at the most selective end, and ask of every branch whether any input can reach it.
- Conditions go wrong at boundaries. Test the threshold and its two neighbours, not convenient round numbers.
- Separate
ifstatements ask "which of these are true?"; a chain asks "which is the first true one?" Any number of the first can run; at most one of the second.
The next chapter looks at the only thing that marks where a branch begins and ends: the indentation.
Where this fits
Part of the Python curriculum.
