Yesterday you made Python say hello. Today you'll make it think. Every program needs to make decisions — take different paths depending on what it receives. Python's tool for that is the if statement.
Here's the simplest version:
def check_temperature(temp):
if temp > 30:
return "hot"
else:
return "comfortable"if evaluates a condition — is temp > 30 true or false? When it's true, Python runs the indented block below it. When it's false, Python skips to else and runs that block instead. The indentation isn't decoration — it is the structure.
So the spaces are doing real work? It won't run right if I get them wrong?
Exactly — and this surprises most beginners coming from other languages. In most languages, structure comes from curly braces { }. Python uses whitespace instead. Four spaces per indentation level is the strong convention. Get it wrong and Python raises an IndentationError, its way of saying the shape of your code doesn't make sense.
It feels strict at first. But it also means Python code tends to look like what it does — the visual structure matches the logical structure.
What if there are more than two options? Like, what if "hot" isn't the only alternative to "comfortable"?
That's where elif comes in — short for "else if." You can chain as many as you need:
def describe_temp(temp):
if temp > 35:
return "scorching"
elif temp > 25:
return "warm"
elif temp > 15:
return "mild"
else:
return "cold"Python reads these conditions from top to bottom and stops the moment one is true. If temp is 38, the first branch fires and all the rest are skipped entirely. else is a safety net — it only runs if every condition above it was false.
So the first matching branch wins and nothing below it runs — even if a later condition would also be true?
That's the key insight, and it's exactly the trap in your challenge today. FizzBuzz: return "fizz" if a number is divisible by 3, "buzz" if divisible by 5, "fizzbuzz" if divisible by both, and the number itself otherwise.
If you check for 3 first, 15 — which is divisible by both — matches n % 3 == 0 and returns "fizz" before it ever reaches the "both" case. The solution is to check the most specific condition first:
# Wrong order — 15 returns "fizz" and never reaches "fizzbuzz"
if n % 3 == 0:
return "fizz"
elif n % 5 == 0:
return "buzz"
elif n % 3 == 0 and n % 5 == 0: # unreachable for 15!
return "fizzbuzz"The % operator is the modulo — it gives you the remainder after division. 15 % 3 is 0 because 15 divides evenly by 3. 16 % 3 is 1. Whenever you need to check divisibility, % is your tool.
I see it — check for "both" first, then the individual cases, then let everything else fall through to the else. The order is the whole trick.
Exactly. That instinct — "what's the most specific case?" — is one experienced programmers apply automatically. You just reasoned your way there on day two. Now go make all three test cases pass.
When you write an if/elif/else chain, you're building a decision tree. Python enters at the top, walks down, and the first condition that evaluates to True wins. Every branch below it is ignored, even if it would also be true.
This sounds obvious until it bites you. Consider:
def grade(score):
if score >= 60:
return "passing"
elif score >= 90:
return "excellent" # never reached — 95 already matched "passing"A score of 95 would return "passing" here, because the >= 60 check fires first. The >= 90 branch is unreachable code — it will never execute. This is a real category of bug, and it's easy to introduce without noticing.
The fix is always to order conditions from most specific to least specific — narrow ranges before wide ones, compound conditions before single ones.
Why Python chose whitespace
Most programming languages use braces ({ }) to group blocks of code. Python's creator, Guido van Rossum, made a deliberate choice to use indentation instead. The argument: good programmers indent their code anyway, so why not make the visual structure be the real structure? It eliminates a whole class of bugs where the indentation and the braces disagree — where code looks like it's inside an if-block but structurally isn't.
You'll either love this or find it maddening for a while. Most people land on love.
The modulo operator
% is one of Python's most useful tools. It gives you the remainder after integer division. 7 % 3 is 1 (7 divided by 3 is 2 remainder 1). 10 % 2 is 0 (10 divides evenly). Whenever you need to check divisibility — every nth item, even/odd, FizzBuzz — % is your operator.
A useful mental model: n % k == 0 means "k divides n with nothing left over," which is exactly the mathematical definition of divisibility.
if/elif/else in practice
Branching logic shows up everywhere: mapping status codes to messages, routing user input to the right handler, computing prices with tiered discounts. The patterns you're learning here — specific before general, test your assumptions about order — apply at every scale.
Sign up to write and run code in this lesson.
Yesterday you made Python say hello. Today you'll make it think. Every program needs to make decisions — take different paths depending on what it receives. Python's tool for that is the if statement.
Here's the simplest version:
def check_temperature(temp):
if temp > 30:
return "hot"
else:
return "comfortable"if evaluates a condition — is temp > 30 true or false? When it's true, Python runs the indented block below it. When it's false, Python skips to else and runs that block instead. The indentation isn't decoration — it is the structure.
So the spaces are doing real work? It won't run right if I get them wrong?
Exactly — and this surprises most beginners coming from other languages. In most languages, structure comes from curly braces { }. Python uses whitespace instead. Four spaces per indentation level is the strong convention. Get it wrong and Python raises an IndentationError, its way of saying the shape of your code doesn't make sense.
It feels strict at first. But it also means Python code tends to look like what it does — the visual structure matches the logical structure.
What if there are more than two options? Like, what if "hot" isn't the only alternative to "comfortable"?
That's where elif comes in — short for "else if." You can chain as many as you need:
def describe_temp(temp):
if temp > 35:
return "scorching"
elif temp > 25:
return "warm"
elif temp > 15:
return "mild"
else:
return "cold"Python reads these conditions from top to bottom and stops the moment one is true. If temp is 38, the first branch fires and all the rest are skipped entirely. else is a safety net — it only runs if every condition above it was false.
So the first matching branch wins and nothing below it runs — even if a later condition would also be true?
That's the key insight, and it's exactly the trap in your challenge today. FizzBuzz: return "fizz" if a number is divisible by 3, "buzz" if divisible by 5, "fizzbuzz" if divisible by both, and the number itself otherwise.
If you check for 3 first, 15 — which is divisible by both — matches n % 3 == 0 and returns "fizz" before it ever reaches the "both" case. The solution is to check the most specific condition first:
# Wrong order — 15 returns "fizz" and never reaches "fizzbuzz"
if n % 3 == 0:
return "fizz"
elif n % 5 == 0:
return "buzz"
elif n % 3 == 0 and n % 5 == 0: # unreachable for 15!
return "fizzbuzz"The % operator is the modulo — it gives you the remainder after division. 15 % 3 is 0 because 15 divides evenly by 3. 16 % 3 is 1. Whenever you need to check divisibility, % is your tool.
I see it — check for "both" first, then the individual cases, then let everything else fall through to the else. The order is the whole trick.
Exactly. That instinct — "what's the most specific case?" — is one experienced programmers apply automatically. You just reasoned your way there on day two. Now go make all three test cases pass.
When you write an if/elif/else chain, you're building a decision tree. Python enters at the top, walks down, and the first condition that evaluates to True wins. Every branch below it is ignored, even if it would also be true.
This sounds obvious until it bites you. Consider:
def grade(score):
if score >= 60:
return "passing"
elif score >= 90:
return "excellent" # never reached — 95 already matched "passing"A score of 95 would return "passing" here, because the >= 60 check fires first. The >= 90 branch is unreachable code — it will never execute. This is a real category of bug, and it's easy to introduce without noticing.
The fix is always to order conditions from most specific to least specific — narrow ranges before wide ones, compound conditions before single ones.
Why Python chose whitespace
Most programming languages use braces ({ }) to group blocks of code. Python's creator, Guido van Rossum, made a deliberate choice to use indentation instead. The argument: good programmers indent their code anyway, so why not make the visual structure be the real structure? It eliminates a whole class of bugs where the indentation and the braces disagree — where code looks like it's inside an if-block but structurally isn't.
You'll either love this or find it maddening for a while. Most people land on love.
The modulo operator
% is one of Python's most useful tools. It gives you the remainder after integer division. 7 % 3 is 1 (7 divided by 3 is 2 remainder 1). 10 % 2 is 0 (10 divides evenly). Whenever you need to check divisibility — every nth item, even/odd, FizzBuzz — % is your operator.
A useful mental model: n % k == 0 means "k divides n with nothing left over," which is exactly the mathematical definition of divisibility.
if/elif/else in practice
Branching logic shows up everywhere: mapping status codes to messages, routing user input to the right handler, computing prices with tiered discounts. The patterns you're learning here — specific before general, test your assumptions about order — apply at every scale.