Conditionals are where a program stops being a list of steps and starts making decisions. Python's version has fewer moving parts than most languages and two features worth knowing that most beginners never meet.
if, elif, else
balance = 9650.50
if balance >= 10000:
tier = "Platinum"
elif balance >= 5000:
tier = "Gold"
elif balance >= 1000:
tier = "Silver"
else:
tier = "Standard"
print(tier) # Output: Gold
The colon opens the block and the indentation decides what is in it. elif is one word —
there is no else if. You may have as many elif branches as you like and at
most one else, which is optional.
Branches are tested top to bottom and the first true one wins, so order matters: put the narrowest
condition first. Swapping the first two lines above would make Platinum unreachable.
The comparison operators
print(5 == 5.0) # Output: True
print("a" != "A") # Output: True
print(3 < 5 <= 5) # Output: True
print("pizza" in "pizzas") # Output: True
3 < 5 <= 5 is a chained comparison, and it means exactly what it
looks like. Most languages would evaluate 3 < 5 to a boolean and then compare
that to 5, giving nonsense. Python chains them properly, so if 0 <= index <
len(items): is the idiomatic range check.
and, or, not — and short-circuiting
Python spells the boolean operators as words.
age, member = 25, True
print(age >= 18 and member) # Output: True
print(age < 18 or member) # Output: True
print(not member) # Output: False
Both and and or short-circuit: they stop as soon as the
answer is known. That is not a performance note, it is how you write a safe guard.
user = None
if user is not None and user["name"] == "Alice":
print("never reached")
print("no crash") # Output: no crash
The right-hand side would raise TypeError, but it never runs because the left side was
already false. Reverse the two halves and the program crashes — so the order of the operands is part
of the logic, not a style choice.
Truthiness
Any value can be tested directly. Empty things and zero are false, everything else is true:
items = []
if not items:
print("cart is empty") # Output: cart is empty
name = "Alice"
if name:
print(f"hello {name}") # Output: hello Alice
if items: is how Python asks "is this non-empty" — not if len(items) > 0:
and definitely not if items != []:.
The trap: this makes 0, "" and None indistinguishable. If
zero is a legitimate value — a balance, a count, a discount — test is not None
explicitly, or you will treat a real zero as a missing one.
The conditional expression
Python's version of the ternary operator reads as an English sentence:
count = 1
label = "item" if count == 1 else "items"
print(f"{count} {label}") # Output: 1 item
balance = -40
status = "overdrawn" if balance < 0 else "ok"
print(status) # Output: overdrawn
Value first, condition second, alternative last. Use it when you are choosing between two
values; use a real if when you are choosing between two actions. Never
nest one inside another — that is the point at which it stops being readable.
match, when a chain of elifs is really a table
match arrived in 3.10. Reaching for it as a switch statement is the common
mistake — it is a pattern matcher, and it earns its place when you are pulling a
shape apart.
def describe(command):
match command:
case ["deposit", amount]:
return f"deposit of {amount}"
case ["transfer", src, dst, amount]:
return f"{amount} from {src} to {dst}"
case ["quit"] | ["exit"]:
return "goodbye"
case _:
return "unknown"
print(describe(["deposit", 100])) # Output: deposit of 100
print(describe(["transfer", 1, 2, 250])) # Output: 250 from 1 to 2
print(describe(["exit"])) # Output: goodbye
print(describe(["nonsense"])) # Output: unknown
Each case matches a structure and binds the pieces to names in one step.
case _ is the default. Doing that with elif would mean checking the length,
then the first element, then unpacking — three steps where this is one.
For a plain equality check against four constants, an if/elif chain is
clearer and works on every Python. Use match when there is structure to destructure.
Flattening a nested if
Nesting conditions to three or four levels is the most common readability problem in beginner code, and it usually has a mechanical fix. This:
def withdraw(account, amount):
if account is not None:
if amount > 0:
if account["balance"] >= amount:
return "ok"
else:
return "insufficient funds"
else:
return "amount must be positive"
else:
return "no account"
print(withdraw({"balance": 100}, 250)) # Output: insufficient funds
says exactly the same thing as this:
def withdraw(account, amount):
if account is None:
return "no account"
if amount <= 0:
return "amount must be positive"
if account["balance"] < amount:
return "insufficient funds"
return "ok"
print(withdraw({"balance": 100}, 250)) # Output: insufficient funds
Handle each failure and leave immediately — a guard clause. The happy path ends up
at the left margin at the bottom, unindented, and each rule sits next to its own error message
instead of miles away in a matching else. Every else disappeared, and the
version you can read at a glance is the second one.
Two things Python does not have
No assignment inside a condition — if x = 5: is a syntax error, by
design, so the classic =-for-== bug cannot happen. When you genuinely want to
test and capture at once, use the walrus:
values = [1, 2, 3, 4]
if (total := sum(values)) > 5:
print(f"total is {total}") # Output: total is 10
No empty block — every if needs a body. When you genuinely want
nothing to happen, pass is the placeholder that says so.
Next: Iteration.