AlgoMaster Logo

if-else-elif Statements

High Priority25 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

Most useful programs need to make decisions: charge shipping if the cart is below a threshold, mark an order as eligible for a discount, refuse to add an item if it's out of stock. The if statement is how Python expresses those decisions. This lesson covers the full shape of conditional branching, the way Python decides which branch to run, and the small set of mistakes that bite almost every beginner.

The if Statement

An if statement runs a block of code only when a condition is true. The shape is always the same: the keyword if, then an expression that produces True or False, then a colon, then an indented block.

Three things in that snippet are doing real work, and skipping any of them produces an error. The colon at the end of the if line tells Python "a block follows". The indentation under it (four spaces, by convention) marks which lines belong to the block. The line print("Done.") is back at the outer indentation, so it runs whether the condition is true or false.

If you forget the colon, Python rejects the file with a SyntaxError:

If you forget the indentation, Python complains too, because the block under an if cannot be empty:

Python uses indentation to define structure, where most other languages use braces. Whatever indentation width you pick for the first line of a block, every line in that block must use exactly the same width. Mixing tabs and spaces is the classic way to break this and get a confusing IndentationError. Pick spaces (PEP 8 recommends four), set your editor to insert spaces when you press Tab, and you'll never see the error.

The block under an if is sometimes called the suite. It can contain one statement or many:

All four lines belong to the same block because they share the same indentation. The moment a line dedents back to the outer level, the block ends.

Adding an else Branch

if on its own handles "do this when the condition is true". Most decisions also need a "do this otherwise" branch. That's what else is for.

The else keyword sits at the same indentation as its if, gets its own colon, and introduces another indented block. Exactly one of the two blocks runs, never both, never neither. The condition decides which one.

You can read if / else out loud almost like English: "If the stock is greater than zero, add to cart; otherwise, mark it out of stock." That's a good check for whether your condition makes sense. If reading it out loud sounds backwards, the condition probably is backwards.

A common pattern is to compute one of two values based on a condition:

That one-line form is a ternary expression. For now, use the full if / else block whenever the body has more than a single value to compute, because it stays readable as logic grows.

The elif Chain

Many decisions have more than two outcomes. Order status, for example, can be placed, shipped, delivered, or cancelled, and each value should produce a different message. Stacking nested if statements works but reads badly. Python gives you elif (short for "else if") for this exact pattern.

You can chain as many elif branches as you need, and the trailing else is optional. Without an else, none of the branches may run, and execution simply continues past the chain.

Here's what's actually happening when Python sees this chain:

Two important things are visible in that flow. Python checks conditions top to bottom, and it stops at the first one that's true. After running the matching branch, it skips every remaining elif and the else, then continues with whatever comes after the chain. No second branch runs, even if its condition would also have been true.

That ordering matters when ranges overlap. Watch what happens with a poorly ordered discount tier:

A $250 cart should have qualified for the 15% tier, but the customer got 5%. The first branch matched (because $250 is greater than $50), and Python stopped there. The fix is to order the checks from most specific to least specific:

Now the >= 200 branch fires first, the rest are skipped, and the customer gets the right tier. The general rule for chained ranges: put the tightest condition at the top.

Truthy and Falsy Conditions

The condition in an if doesn't have to be a real True or False. Python accepts any value and asks: "does this count as true?". Every value in the language has an answer. The values that count as false are called falsy, and the rest are truthy.

The full list of falsy values is short:

  • False
  • None
  • Zero of any numeric type: 0, 0.0, 0j
  • Empty containers: "", [], {}, (), set()
  • Anything that defines __bool__ to return False or __len__ to return 0

Everything else is truthy. That includes negative numbers, the string "False", the list [0], and any non-empty container.

The string "anything" is truthy because it isn't empty. The list [0] is truthy because it has one element, even though that element is itself falsy. -5 is truthy because the only falsy number is zero.

This rule turns "is this collection non-empty?" into a one-liner. Compare the verbose version with the Pythonic version:

Both work, both are correct, and both produce identical output. The second form is what experienced Python developers write because it reads naturally and works for every container type without thinking about which one. If cart is an empty list, an empty tuple, or an empty set, if cart: is False. Any item, of any type, makes it True.

The same idiom shows up everywhere:

Each if reads almost like English: "if no customer name", "if a discount code is set", "if the wishlist is empty", "if there are ratings". No need to write len(...) > 0 or != None or != "" for any of them. Python's truthiness rules cover all four.

There is one place where you need to be careful with this style: distinguishing "no value" from "the value is zero". If discount could legitimately be 0, then if discount: treats that as missing, which is wrong. In that case, compare explicitly: if discount is not None:.

The customer's discount is 0, which is a real value, but if discount: treats it as falsy and the wrong branch runs. Use if discount is not None: when zero is meaningful.

Combining Conditions

Real decisions usually depend on more than one fact. The Boolean operators and, or, and not are what tie multiple conditions together inside an if.

The key behavior is short-circuit evaluation. and stops at the first falsy operand, or stops at the first truthy one. That's not just a performance optimization. It also lets you safely guard a check that would otherwise crash:

If discount_code is None, calling .startswith on it would raise an AttributeError. The and operator never gets that far: the left side is None (falsy), so the right side is never evaluated. Order matters here. Reverse the operands and the same code crashes:

Always put the cheap, safety-checking condition first when you're guarding against a value that might not exist.

or works the same way, just with the test flipped: it stops at the first truthy operand. That makes it the natural choice for "any of these is enough":

The cart total alone wouldn't qualify, but the customer is a member, so the second operand is true and the rest of the chain is skipped. Three conditions, any of which would trigger free shipping, expressed in one readable line.

not flips a single condition. The two forms below mean the same thing, and the second one is usually nicer:

not cart reads as "if there's nothing in the cart" and works for any empty container. The == [] form only handles lists, and you'd have to write a different version for empty strings, sets, or dictionaries.

Chained Comparisons in Conditions

Chained comparisons are especially useful inside if statements because they map cleanly to range checks:

0 < discount < 1 reads exactly like the math notation and means 0 < discount and discount < 1. The variable discount is evaluated only once. Use this form for any "is this value in a range?" check; it's shorter and clearer than two separate comparisons joined by and.

Nested if vs Flat elif Chains

When one decision depends on another, beginners often reach for a nested if. Sometimes that's the right call. More often, the same logic reads better as a flat chain or a guard-clause style.

Here's a nested version that decides what message to show on a product page based on stock and price:

It works, but the deep indentation makes the logic hard to follow. Each level of nesting adds visual weight without adding clarity. Flatten it by combining the conditions:

Same behavior, much easier to scan. Each branch states its full condition on one line, and there's no nesting to track in your head.

Another pattern that often beats nesting is the guard clause: handle the special cases up front and bail out, then write the main logic at the outer level. Inside a function, this usually means returning early. Outside a function, it means assigning a value and using a chain that handles the easy rejections first.

The "out of stock" case is the most restrictive, so it goes first. After that, the rest of the chain only deals with the "in stock" case in two flavors. The result is a single linear flow of branches with no nesting at all.

Nesting is fine when the inner decision is genuinely meaningful only when the outer condition holds, and especially when the inner decision has its own multi-branch chain. The rule of thumb: if you can replace nesting with and / or and still read the code aloud naturally, flatten it.

Common Mistakes

A handful of bugs come up over and over with if statements. Each one is small, each one feels obvious in hindsight, and each one will catch you at some point.

Using = Instead of ==

In an if condition, you want to compare with ==, not assign with =. Python catches this and raises a SyntaxError, which is helpful but still surprises people coming from languages where the same mistake silently compiles.

The fix is to double up the equals sign:

Comparing Against Multiple Values the Wrong Way

This one is sneakier because it doesn't raise an error. The bug runs cleanly and produces the wrong answer.

What's wrong with this code?

The order has status "placed", so the message should say it's still being processed. Instead, it falsely claims the order is in transit. The bug is in the condition.

status == "shipped" or "delivered" parses as (status == "shipped") or "delivered". The first half is False, so or evaluates the second half, which is the string "delivered". Non-empty strings are truthy, so the whole expression is truthy, and the wrong branch runs. The string "delivered" is not being compared against status at all; it's just sitting there as a standalone value.

The fix is to compare each value explicitly, or use in against a collection of accepted values:

The in form is shorter, scales to as many values as you want, and avoids the trap entirely. Reach for it whenever you're checking "is this value one of these few options?".

Forgetting That if items: Is the Pythonic Empty Check

A long-form check like if len(cart) > 0: works correctly, but it's noisier than necessary. Worse, beginners sometimes write if cart != None and cart != [] and len(cart) > 0:, which over-checks the same thing three different ways.

Both versions print the same thing, but the second one is much easier to read and works for every container type. Lean on Python's truthiness rules.

Comparing Floats With ==

Floating-point arithmetic is approximate, so two values that look equal on paper might not be equal in memory.

0.1 + 0.2 doesn't produce exactly 0.3 in binary floating point. For prices and totals where small rounding errors matter, either round before comparing or use math.isclose for tolerance. The Numbers section covered this in detail; the takeaway here is just to remember it inside if conditions.

Dangling else After an elif Chain

The else at the end of an if / elif chain attaches to the whole chain, not to any specific elif. That's usually what you want, but it's easy to forget when you read the code top-to-bottom.

If status were "cancelled" and you expected the else to handle that as an "unknown" case, congratulations, that's exactly what it does. But if you intended else to fire only when status == "delivered" was false (and the rest had matched), you've misread the structure. Each elif is a fresh top-level test against the original condition, and the else runs only when none of them matched.

When in doubt, list every value you care about explicitly and let the else catch only the genuine unknowns. That makes the intent obvious to anyone reading the chain.

Quiz

if-else-elif Statements Quiz

10 quizzes