Removing Redundant Conditions
Concepts covered: Boolean Operators
A condition is redundant when deleting it changes nothing the program does. For every value that can reach the line, the same branch runs, the same variables end with the same values, and the same exceptions are raised or avoided. Redundant checks cost the next reader time. Someone who sees amount < 1000 on a branch assumes an amount of 1000 or more can arrive there, and goes looking for a case that never comes.
Facts an elif already knows
An elif test runs only after every test above it came out False. Each of those False results is a fact the branch gets for free, and you can delete a check that restates one of them.
if amount >= 1000:
tier = "large"
elif amount >= 100 and amount < 1000:
tier = "medium"
elif amount >= 0 and amount < 100:
tier = "small"
else:
tier = "invalid"By the second test, amount >= 1000 has already failed, so amount < 1000 is True every time Python reaches it. By the third test, amount >= 100 has failed as well, which guarantees amount < 100. Both halves can go, and the chain becomes 3 cut points:
if amount >= 1000:
tier = "large"
elif amount >= 100:
tier = "medium"
elif amount >= 0:
tier = "small"
else:
tier = "invalid"The reasoning says the rewrite is safe, and boundary values confirm it. Run both versions on each value where a comparison flips, taking 1 step either side of every cut point:
| amount | original tier | simplified tier |
|---|---|---|
| -1 | invalid | invalid |
| 0 | small | small |
| 99 | small | small |
| 100 | medium | medium |
| 999 | medium | medium |
| 1000 | large | large |
3 more shapes of a dead check
- Absorbed inside a single operator. In x > 100 and x > 50, the stricter test implies the looser one, so keep x > 100. Under or the looser test wins: x > 100 or x > 50 is x > 50.
- A branch that can never run. elif amount >= 5000: placed below if amount >= 1000: is dead, because the branch above it already took every amount it would catch.
- An if that copies its own test. if late: alert = True paired with else: alert = False collapses to alert = late when late is always a bool, and to alert = bool(late) when it could hold anything else.
Checks that only look redundant
Short-circuit evaluation gives some checks a second job. In fee is not None and fee > 0, Python evaluates the right test only when the left one is True. If you delete the left test, a None fee no longer produces a quiet False. It reaches fee > 0 and raises:
fee = None
print(fee is not None and fee > 0)
try:
print(fee > 0)
except TypeError as err:
print("TypeError:", err)Output:
False TypeError: '>' not supported between instances of 'NoneType' and 'int'
Now compare fee is not None and fee with plain fee as the test of an if. None is falsy, so fee alone already sends it down the False branch, and nothing on the right can raise. In that position the None test is redundant. Stored in a variable, the 2 forms differ: for a None fee, the first evaluates to False and the second to None, so deleting the test changes a value. The same test is needed in one place and redundant in another, so every removal needs its own argument.
A related look-alike: if is_refund == True: and if is_refund: agree only when is_refund always holds a bool. 1 == True is True, but "yes" == True is False even though "yes" is truthy. The shorter test would let through strings that the longer one refused.
Which part of the third test can be deleted without changing status for any value of rows, including None?
if rows is None:
status = "missing"
elif rows == 0:
status = "empty"
elif rows is not None and rows > 0:
status = "loaded"
else:
status = "corrupt"- rows is not None: the first branch already took every None, so it is always True here
- rows > 0: the second branch already handled zero, so every remaining count is positive
- Both tests, since the 2 earlier branches together cover None and zero
- Neither: rows is not None stops rows > 0 from raising a TypeError
In that question the guard was dead because an earlier branch had already taken every None. Put no None branch above it, and the same words become the only thing between a None value and <. Whether a guard is removable depends on what sits above it, never on the guard's own wording.
About this lesson
This section is part of the Control Flow: Advanced lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.