The loyalty card promises a free coffee on the tenth visit, but only before eleven and never on a holiday. One if handles the first rule; the second starts to stack. Where does indentation decide which lines belong together?
A program becomes useful when it can react to different values. Conditionals run a block only when a test is true. Python uses indentation to show which lines belong to each branch. You will also replace deep nesting with guard clauses and test boundary values.
Comparisons produce booleans
Use == for equality, != for difference, and <, <=, >, >= for ordering. A comparison produces True or False. Do not confuse = (assignment) with == (comparison).
score = 85
print(score >= 80)
print(score == 100)
print(score != 0)Comparison results
Choose one branch
if runs when its condition is true. elif is checked only when the previous conditions were false. else catches everything that remains. Order matters: put the narrowest or highest threshold first.
score = 85
if score >= 90:
print("A")
elif score >= 80:
print("B")
else:
print("C")A grade ladder
Combine conditions
and is true only when both sides are true. or is true when at least one side is true. not flips a boolean. Parentheses make complex conditions easier to read.
age = 20
member = True
print(age >= 18 and member)
print(age < 18 or member)
print(not member)Boolean logic
Guard clauses keep nesting shallow
When a function has several preconditions, check them first and return early. This is a guard clause. The main path stays flat, and each rejection is close to the condition that caused it.
def shipping(total, member=False):
if total <= 0:
return "invalid total"
if total >= 50 or member:
return "free shipping"
return "shipping: 5.00"
for total in [0, 42, 50]:
print(total, shipping(total))
print(50, shipping(50, member=True))Early returns instead of nested branches
Boundaries and explicit truth tests
Boundaries deserve explicit tests. if total treats 0 as false, which may hide an invalid order. Prefer a comparison such as total > 0 when zero means missing data. Keep mutually exclusive cases in one if/elif/else chain.
for total in [0, 49.99, 50]:
if total <= 0:
print(total, "invalid")
elif total < 50:
print(total, "paid shipping")
else:
print(total, "free shipping")Test the values around a threshold
Use guard clauses
Deep nesting makes the main path hard to see. A guard clause handles invalid input first and returns immediately, so the rest of the function stays flat. Use guards for preconditions, not for hiding complex branches. The reader should be able to scan from the top and see one normal path.
def shipping(weight):
if weight <= 0:
return "invalid"
if weight < 1:
return "small"
return "standard"
for w in (0, 0.5, 3):
print(w, shipping(w))Invalid cases leave early
Key points
- == compares values; = assigns a value.
- Only the first matching branch runs.
- elif avoids checking later conditions after a match.
- and needs both sides; or needs at least one side.
- Check invalid values first; guard clauses keep the main path readable.
- Handle invalid input first so the main path stays unindented.
Common mistakes
- Writing if score = 90 instead of if score == 90.
- Putting a low threshold before a high threshold in an elif chain.
- Forgetting the colon or the indented block.
- Using separate if statements when the branches are mutually exclusive.
- Nesting the normal path inside three levels of if statements.
Print a shipping message: free shipping for orders over 50, otherwise show the fee.
Try it yourself
Change the values, predict the output, then run the code. The Python runtime is downloaded the first time you press Run, so the first run can take a few seconds.
Practice this lesson
12 questions with immediate explanations.