All 57 lessons

Lesson 8 of 57 · Control Flow

Making Decisions with if, elif and else

By Skillancy Editorial Team

Run the code in the free Python Compiler or on your own computer. Lessons that read files use the FreshBasket practice data: download the dataset (ZIP).

A program that does the same thing every time is a calculator. A program that decides is software. In FreshBasket's world, decisions are everywhere: Which delivery fee applies to this basket? Is this order late, on time or express? Does this customer qualify for the Prime offer? Each decision becomes a few lines of if, elif and else.

This lesson shows how to write those decisions cleanly. You will learn the syntax, how Python uses indentation to group lines, how to chain several conditions with elif, when to nest, how to write a compact one-line choice, and how the modern match statement helps with many fixed options. You will also see the three mistakes that cause most if bugs.

The shape of an if statement

if condition:
    # runs when the condition is true
    ...
elif other_condition:
    # runs when the first was false and this one is true
    ...
else:
    # runs when none of the above were true
    ...

Three details matter. First, each header ends with a colon. Second, the lines that belong to the branch are indented, normally by four spaces; the indentation is how Python knows where the branch ends. Third, Python tests the conditions from top to bottom and stops at the first true one, then skips everything else in the chain. elif and else are optional, and you can have as many elif branches as you like.

Worked examples

Example 1: A simple if-else

Question: Mark a delivery as within promise or late.

minutes = 34

if minutes <= 30:
    verdict = "Within promise"
else:
    verdict = "Late"

print(f"{minutes} min -> {verdict}")

Output

34 min -> Late

How it works: Python evaluates minutes <= 30. It is false for 34, so the else branch runs and assigns "Late". Only one of the two branches ever runs.

Reading the result: The output is 34 min -> Late. Change minutes to 22 and you get the other branch. Notice that we assigned a variable in each branch and printed after the if; this is a common and tidy pattern.

Example 2: An elif ladder

Question: Classify deliveries into Express (under 15), Fast (15 to 24), Standard (25 to 30) or Late (over 30).

def classify(minutes):
    if minutes < 15:
        return "Express"
    elif minutes < 25:
        return "Fast"
    elif minutes <= 30:
        return "Standard"
    else:
        return "Late"

for m in [9, 15, 24, 25, 30, 31]:
    print(m, "->", classify(m))

Output

9 -> Express
15 -> Fast
24 -> Fast
25 -> Standard
30 -> Standard
31 -> Late

How it works: Each condition is checked in order. The check minutes < 25 does not need to say "and at least 15", because any value below 15 was already handled by the first branch. The return statement ends the function as soon as a branch matches.

Reading the result: The six test values land in the right buckets, and the boundaries behave as intended: 15 is Fast, 25 is Standard, 30 is Standard and 31 is Late. Always test the exact boundaries of your conditions, because that is where off-by-one mistakes hide.

Example 3: Order of conditions matters

Question: What goes wrong if we check conditions in the wrong order?

def tier_wrong(spend):
    if spend >= 1000:
        return "Silver"
    elif spend >= 5000:
        return "Gold"
    else:
        return "Basic"

def tier_right(spend):
    if spend >= 5000:
        return "Gold"
    elif spend >= 1000:
        return "Silver"
    else:
        return "Basic"

print(tier_wrong(7200), tier_right(7200))

Output

Silver Gold

How it works: A spend of ₹7,200 satisfies both >= 1000 and >= 5000. Python stops at the first true branch, so in tier_wrong the Gold branch can never be reached. In tier_right we test the most demanding condition first.

Reading the result: The wrong version labels the customer Silver while the correct one says Gold. When conditions overlap, order them from the most specific or strictest to the most general.

Example 4: Delivery fee rules

Question: The delivery fee is ₹0 for Prime members, ₹0 for baskets of ₹299 or more, ₹15 for baskets between ₹150 and ₹298, and ₹30 otherwise. Compute the fee for several customers.

def delivery_fee(basket, is_prime):
    if is_prime or basket >= 299:
        return 0
    elif basket >= 150:
        return 15
    else:
        return 30

customers = [(120, False), (200, False), (350, False), (90, True)]
for basket, prime in customers:
    print(f"basket={basket:>3}, prime={prime!s:<5} -> fee Rs. {delivery_fee(basket, prime)}")

Output

basket=120, prime=False -> fee Rs. 30
basket=200, prime=False -> fee Rs. 15
basket=350, prime=False -> fee Rs. 0
basket= 90, prime=True  -> fee Rs. 0

How it works: The first branch combines two free-delivery reasons with or. The remaining branches only run for non-Prime customers with a basket under 299. We loop through a list of pairs and unpack each pair into basket and prime.

Reading the result: The fees are 30, 15, 0 and 0. Business rules translate almost word for word into code when you keep each branch simple.

Example 5: Nested conditions and guard clauses

Question: Decide an order's refund action: cancelled orders get a full refund if paid online, nothing if COD; returned orders get a refund only if reported within 2 days.

def refund_nested(status, payment, days_since):
    if status == "Cancelled":
        if payment != "COD":
            return "Full refund"
        else:
            return "No payment taken"
    elif status == "Returned":
        if days_since <= 2:
            return "Refund approved"
        else:
            return "Refund window closed"
    else:
        return "No action"

def refund_flat(status, payment, days_since):
    if status == "Cancelled" and payment != "COD":
        return "Full refund"
    if status == "Cancelled":
        return "No payment taken"
    if status == "Returned" and days_since <= 2:
        return "Refund approved"
    if status == "Returned":
        return "Refund window closed"
    return "No action"

cases = [("Cancelled", "UPI", 0), ("Cancelled", "COD", 0), ("Returned", "Card", 1), ("Returned", "Card", 5), ("Delivered", "UPI", 0)]
for c in cases:
    assert refund_nested(*c) == refund_flat(*c)
    print(c, "->", refund_flat(*c))

Output

('Cancelled', 'UPI', 0) -> Full refund
('Cancelled', 'COD', 0) -> No payment taken
('Returned', 'Card', 1) -> Refund approved
('Returned', 'Card', 5) -> Refund window closed
('Delivered', 'UPI', 0) -> No action

How it works: The nested version places an if inside another if, which mirrors how people describe the rule but gets deep quickly. The flat version uses early returns ("guard clauses"): handle each special case and leave, so the main logic stays at one indentation level. The assert line proves that both versions agree on every case; if they ever disagreed, Python would stop with an AssertionError.

Reading the result: Both functions return the same answer for all five orders: full refund, no payment taken, refund approved, window closed, and no action. Prefer the flat style once you have more than two levels of nesting.

Example 6: The one-line conditional expression

Question: Create a label that says "Prime" or "Regular" in a single line, and compute a capped discount.

is_prime = True
label = "Prime" if is_prime else "Regular"
print(label)

requested_discount = 35
applied = requested_discount if requested_discount <= 25 else 25
print(applied)

orders = 0
print("No orders" if not orders else f"{orders} orders")

Output

Prime
25
No orders

How it works: The form value_if_true if condition else value_if_false is an expression, so it can be used right inside an assignment or a call. It is ideal for choosing between two simple values. The last line also uses truthiness: not orders is true when orders is zero.

Reading the result: The label is Prime, the discount of 35% was capped at 25%, and the empty-orders message prints because zero is falsy. Do not nest several conditional expressions in one line; if you need more than two outcomes, use if/elif.

Example 7: Truthiness in conditions

Question: How can I test "is there anything in this list or string" quickly?

cart = []
if cart:
    print("Cart has items")
else:
    print("Cart is empty")

cart.append("Masala Chai 500g")
if cart:
    print(f"Cart has {len(cart)} item(s)")

coupon = ""
if not coupon:
    print("No coupon applied")

Output

Cart is empty
Cart has 1 item(s)
No coupon applied

How it works: Empty containers and empty strings are falsy, and non-empty ones are truthy. if cart: is therefore the Pythonic version of if len(cart) > 0:. Calling cart.append(...) adds an element to a list.

Reading the result: The first check prints that the cart is empty, the second that it has 1 item after the append, and the third that no coupon applies. Be careful with numbers: if discount: is false when the discount is 0, which may or may not be what you want.

Example 8: match for many fixed options

Question: Map a payment method to a processing fee rate with Python 3.10's match statement.

def fee_rate(method):
    match method:
        case "UPI":
            return 0.0
        case "Card" | "Wallet":
            return 0.02
        case "COD":
            return 0.015
        case _:
            return None

for m in ["UPI", "Card", "Wallet", "COD", "NetBanking"]:
    print(m, fee_rate(m))

Output

UPI 0.0
Card 0.02
Wallet 0.02
COD 0.015
NetBanking None

How it works: match compares the value against each case in order. The pipe | means "either of these", and the underscore _ is the catch-all, like else. For a handful of fixed values it can be tidier than elif, but a dictionary lookup (next module) is often even better.

Reading the result: UPI is free, Card and Wallet both cost 2%, COD costs 1.5%, and the unknown method falls through to None. match needs Python 3.10 or newer, so if you are on an older version, use elif.

Example 9: Checking for missing data before deciding

Question: An order's delivery time may be missing (None) if it was cancelled. Write a safe classification.

def sla_status(minutes):
    if minutes is None:
        return "No delivery"
    if minutes <= 30:
        return "On time"
    return "Late"

for value in [24, 41, None]:
    print(value, "->", sla_status(value))

Output

24 -> On time
41 -> Late
None -> No delivery

How it works: The first guard returns early if the value is None. Without it, the comparison None <= 30 would raise a TypeError, because Python refuses to order None against a number.

Reading the result: The three inputs produce On time, Late and No delivery. In pandas, a missing number is NaN, and comparisons with NaN quietly give False, so you must test for missing values explicitly there too.

Common mistakes

1. Missing colon.

if 5 > 3
    print("yes")

Output

  File "example.py", line 1
      if 5 > 3
              ^
  SyntaxError: expected ':'

2. Wrong indentation. A line that is not indented under the if always runs, no matter what the condition says.

stock = 0
if stock > 0:
    print("In stock")
print("This line always prints")

Output

This line always prints

3. Using = instead of ==. Assignment is not allowed inside a plain if condition, so Python reports a syntax error.

4. Overlapping conditions in the wrong order. Example 3 showed how an easy-to-satisfy test hides a stricter one.

5. Repeating the same test in every branch. If every branch starts with if is_prime and ..., restructure with a nested block or an early return.

Practice questions

Practice 1 (Easy): Even or odd batch

Print "even" if the number of orders in a batch (here, 37) is even, otherwise "odd".

Show solution
batch = 37
if batch % 2 == 0:
    print("even")
else:
    print("odd")

Output

odd

The remainder after dividing by 2 is 0 for even numbers. Since 37 leaves a remainder of 1, the answer is odd.

Practice 2 (Medium): Grade the rating

Convert a star rating into a label: 5 or 4 means "Happy", 3 means "Neutral", and 1 or 2 means "Unhappy". Anything else is "Invalid". Test ratings 5, 3, 1 and 9.

Show solution
def rating_label(r):
    if r in (4, 5):
        return "Happy"
    elif r == 3:
        return "Neutral"
    elif r in (1, 2):
        return "Unhappy"
    else:
        return "Invalid"

print([rating_label(r) for r in (5, 3, 1, 9)])

Output

['Happy', 'Neutral', 'Unhappy', 'Invalid']

Using in with a tuple avoids repeating r == 4 or r == 5. The final else catches impossible values so bad data does not slip through silently.

Practice 3 (Hard): Surge pricing

A delivery has a base fee of ₹20. It is doubled if it is raining. After that, add ₹10 if the hour is between 18 and 21 inclusive (evening rush). Prime members pay half of the final amount. Return the fee for: (raining, hour 19, not Prime), (not raining, hour 11, Prime), and (raining, hour 8, Prime).

Show solution
def surge_fee(raining, hour, is_prime):
    fee = 20
    if raining:
        fee *= 2
    if 18 <= hour <= 21:
        fee += 10
    if is_prime:
        fee /= 2
    return fee

print(surge_fee(True, 19, False))
print(surge_fee(False, 11, True))
print(surge_fee(True, 8, True))

Output

50
10.0
20.0

The three independent if statements (not elif) each adjust the fee, in the stated order: 20 doubles to 40, plus 10 gives 50 for the first customer. The second customer pays half of 20, and the third half of 40. Use separate ifs when several rules can apply at once.

Key takeaways

  • if, elif and else choose one branch; each header ends with a colon and its block is indented.
  • Python tests conditions from top to bottom and stops at the first true one, so order overlapping conditions from strictest to loosest.
  • Use separate if statements when several rules can apply together.
  • Flatten deep nesting with guard clauses and early returns.
  • a if condition else b picks between two simple values; match handles many fixed options.
  • Test boundary values, and check for None before comparing numbers.

Frequently asked questions

What is the difference between several ifs and an if/elif chain?

An if/elif chain runs at most one branch. Separate if statements are each tested independently, so several can run. Use separate ifs when the rules stack, as in the surge-pricing practice.

Do I need an else branch?

No. Leave it out if there is nothing to do otherwise. Include it when you want to be explicit about the default case, or to catch unexpected values.

How many spaces should I indent?

Use four spaces per level, the standard from PEP 8. Never mix tabs and spaces in one file; configure your editor to insert spaces when you press Tab.

Can I put an if statement on one line?

You can write if x: print(x), but style guides discourage it. For simple choices of value, use the conditional expression instead.

Is match the same as a switch statement in other languages?

Similar for simple cases, but Python's match can also destructure lists and dictionaries and add if guards. For plain value lookups, a dictionary is usually more concise.

Why does my if compare to None fail with a TypeError?

Ordering comparisons (<, >) between None and a number are not allowed. Check value is None first, as in the SLA example.

Newsletter

Keep learning Python

One email a week with Python and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.

Ready to practise? Try the Python interview questions