Python – Conditional Statements

July 10, 20264 min readUpdated 8/20/2026

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 conditionif 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.