Conditionals
Conditionals let your program choose what to do. The shape is
if → optional elifs → optional else:
temp = 30
if temp > 25:
print("hot")
elif temp > 15:
print("mild")
else:
print("cold")Python checks each condition top to bottom and runs the first block whose
condition is True. The rest are skipped. Remember: the block is defined by
indentation (4 spaces).
Comparison operators
These produce a bool (True/False):
a == b # equal (two equals signs! one '=' is assignment)
a != b # not equal
a < b # less than
a <= b # less than or equal
a > b # greater than
a >= b # greater than or equal
=vs==is the classic beginner bug.x = 5assigns 5 to x.x == 5asks "is x equal to 5?". Conditions always use==.
❓ Cross-question — "Where's
===? Is==loose like JavaScript's?" Neither exists here. Python's==compares values with no type coercion (1 == "1"isFalse, never a silent match). "Same object" isis— the rough equivalent of JS===on objects. Rule: use==for values, andisonly forNone.
Combining conditions: and / or / not
Three tiny words combine True/False values:
and→Trueonly if both sides are trueor→Trueif at least one side is truenot→ flips it (not TrueisFalse)
In a real condition each side is usually a comparison:
Chaining many conditions with parentheses
You can combine as many as you like and use parentheses to group them — read an expression from the inside out, just like math. Take this one:
It runs the if branch. Here's why, step by step — run this to watch each piece
collapse to a single True/False:
andneeds every value to be true — oneFalsemakes the wholeandfalse (that's whyrightisFalse).orneeds just one true value — soleft or rightisTrue, and theifruns.
Precedence: with no parentheses,
notruns first, thenand, thenor. SoTrue or True and FalseisTrue(theandbinds tighter). When in doubt, add parentheses — they cost nothing and make your intent obvious.
Try it: in the big condition above, flip some
Trues toFalseand Run. Can you land on theelsebranch? (You'll have to make both the left group and the right group false.)
Truthiness
Any value can be tested directly. These count as False: False, 0,
0.0, "" (empty string), [] (empty list), None. Everything else is
True:
name = ""
if name: # empty string is falsy
print("hi", name)
else:
print("no name given")The list is worth memorising, because one entry catches nearly everyone:
if "0":
print("truthy")
else:
print("falsy")❓ Cross-question — "Are empty collections falsy like in JS?" Careful — more things are falsy here. In JS
[]and{}are truthy; in Python[],{},"",0, andNoneare all falsy. Soif items:cleanly reads as "is the list non-empty?" — a very common Python idiom that surprises JS devs.
The pattern: map an input to a result
Here's the same shape applied to shipping costs — study how it's built, then you'll write your own for discount codes:
def shipping_cost(country):
if country == "US":
return 5.00
elif country == "CA":
return 8.00
else:
return 15.00 # everywhere elseNotice how return inside an if exits the function immediately — once a
branch returns, none of the others run. That's exactly the structure you need for
the discount codes below: compare the code, return the right price, and fall
back to else for unknown codes.
Python's switch: match / case
Many languages have a switch statement for picking one option out of several.
Python's version is match / case. When you're comparing one value
against a set of fixed options, it can read more cleanly than a long if/elif
chain:
def shipping_cost(country):
match country:
case "US":
return 5.00
case "CA":
return 8.00
case _: # _ is the catch-all — it works like `else`
return 15.00Read it as: "match country against these cases, run the first that fits, and
case _ catches everything else." It does the same job as the if/elif/else
version above — pick whichever reads more clearly.
You can even match several values in one case with | (read it as "or"):
match code:
case "SAVE10" | "SUMMER": # either code → 10% off
return 0.10
case "HALF":
return 0.50
case _:
return 0.0⚠️ Version note:
match/caseneeds Python 3.10 or newer. This playground runs Python 3.14 (via the project's.venv), so it works here — you'll use it in Task 2 below. The one gotcha: if you ever run the app on Python 3.9,matchraises aSyntaxError.
❓ Cross-question — "Is
matchjust aswitch? Does it fall through?" No fall-through — only the first matchingcaseruns, nobreakneeded. And it's more powerful than a C/JSswitch: it destructures tuples, lists, and dicts (structural pattern matching) — closer to handling a TypeScript discriminated union than a plain value switch.
Idioms & real-world patterns
You know if/elif/else and match. Here are the shorter, sharper ways Python
developers actually write decisions every day.
The ternary — an if that returns a value
When you just need to pick between two values, the one-line conditional
expression (the ternary) beats a four-line if:
Read it left-to-right: "big if bill > 50 else small."
Coming from Java/JS: the parts are re-ordered.
cond ? a : bbecomesa if cond else b— the value comes first, the condition in the middle. No?or:.
x or default — fill in a missing value
or returns the first truthy operand (not just True/False), so it's the
Python way to supply a fallback:
and mirrors it (returns the first falsy operand). Both short-circuit —
the right side is skipped once the left decides the answer.
Coming from Java/JS: same short-circuit as
||/&&, but they return the operand, not a boolean. Mind the falsy trap:count or 10replaces a real0with10. When0is valid, usecount if count is not None else 10.
❓ Cross-question — "Is
x or defaultthe same as TypeScript'sx ?? default?" Close, but not identical. TS's??triggers only onnull/undefined; Python'sortriggers on any falsy value (0,"",[]). For a true None-only fallback, writex if x is not None else default. Python has no?.or??.
Chained comparisons — low < x < high
Python chains comparisons the way maths does — no and needed:
Coming from Java/JS:
0 <= x <= 100is a syntax error there — you'd writex >= 0 && x <= 100. In Python it's one expression, andxis evaluated once.
Guard clauses — return early, stop nesting
Check the bad cases first and return, instead of wrapping the happy path in
ever-deeper ifs. Flat code reads better:
any() and all() — one question over a whole list
any(iterable) is True if at least one item is truthy; all(iterable) is
True only if every item is. They replace a manual loop-with-a-flag:
Real-world — validate an order form with guards + all:
⚡ Advanced — the walrus := and richer match
The walrus operator := assigns and returns a value at once — handy when
you'd otherwise compute something twice:
match also destructures sequences and dicts, and takes a case ... if guard:
🎯 Your turn
Two functions, the same decision written two ways. Check runs the tests for both — implement each with the style named.
Task 1 — apply_discount(bill, code) with if / elif / else. Returns the
bill after the code, rounded to two decimals. Codes: SAVE10 10% off ·
STUDENT 15% off · HALF 50% off · anything else no discount.
apply_discount(100, "SAVE10")→90.0apply_discount(100, "")→100.0
Task 2 — discount_rate(code) with match / case. Returns the rate itself
(a number) — a case per code, and case _ for the rest.
discount_rate("SAVE10")→0.1discount_rate("NOPE")→0.0
Hint — Task 1 chains if/elif/else (a 10% discount means you pay 90%,
bill * 0.90). Task 2 is match code: with a case "SAVE10": per code and a
final case _: catch-all.
Then press ▶ Run, tap the Live App try chips (there's a card for each function), and hit ✓ Check. All green = you've done it both ways. ✅
