Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. Python if, elif and else: conditionals explained
Learn

Python if, elif and else: conditionals explained

Python if else statements run code only when a condition is true. How elif chains are checked, truthy values, the 'or' trap, and match-case.

By Max Arthur
Co-Founder & Content Marketer·October 23, 2026·9 Min read
White text reading 'if, elif and else in Python' on a dark purple dotted background

In Python, if runs a block of code only when its condition is true. elif (short for "else if") adds more conditions to check in order, and else catches everything that matched none of them:

temperature = 23

if temperature > 30:
    print("hot")
elif temperature > 15:
    print("warm")
else:
    print("cold")
warm

Python checks the conditions from top to bottom and runs only the first branch whose condition is true. Once a branch runs, the rest are skipped, even ones that would also have matched. Both elif and else are optional, and you can have as many elif branches as you need.

That one rule, first true branch wins, explains most if-else bugs. The rest come from what Python counts as "true", which is broader than True. Every output below was measured in Pyodide running Python 3.13.2.

Python if else syntax, and the errors you will meet

Each line that starts a branch ends with a colon, and the code that belongs to it is indented underneath. Python uses the indentation, not brackets, to decide where a branch ends. Here is what Python 3.13 says for the common slips:

You wrote Python says
else if y: SyntaxError: expected ':'
if x = 5: SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
if x > 5 with no colon SyntaxError: expected ':'
an elif after the else SyntaxError: invalid syntax
nothing indented under the if IndentationError: expected an indented block after 'if' statement on line 1

The first row confuses people coming from JavaScript or C. Python reads else, expects a colon straight after it, and finds if instead. The keyword is elif, one word.

The = row shows Python being helpful: a single = assigns, == compares, and the message says so. If a branch should do nothing yet, write pass as its body. The last row and its cousins are covered in more depth in fixing IndentationError.

Order matters: the first true branch wins

def grade(score):
    if score >= 50:
        return "pass"
    elif score >= 90:
        return "distinction"
    else:
        return "fail"

print(grade(95))   # pass

A score of 95 should be a distinction, but 95 >= 50 is true, so the first branch wins and the distinction branch can never run for any score. No error, no warning: the code is valid, just unreachable.

The fix is to check the most specific condition first:

def grade(score):
    if score >= 90:
        return "distinction"
    elif score >= 50:
        return "pass"
    else:
        return "fail"

print(grade(95))   # distinction

For ranges, go from the highest threshold down with >=, or from the lowest up with <. Mixing the two directions in one chain is how gaps and overlaps sneak in.

Truthy and falsy: what counts as true

An if does not need a comparison. Any value can be a condition, and Python decides whether it counts as true:

Value As a condition
0, 0.0, 0j false
"" (empty string) false
[], {}, (), set() false
None, False false
" " (a space) true
"0", "False" true
[0] (a list containing zero) true

The rule: zero, empty and None are false, and almost everything else is true. Strings are true whenever they contain anything at all, which catches people reading settings from files or forms:

config = {"debug": "false"}
if config["debug"]:
    print("debug mode on")
debug mode on

The string "false" is not empty, so it is true. Compare it explicitly, as in config["debug"] == "true", or convert it properly when you load it.

Truthiness is what makes if items: the standard way to check that a list has something in it. It reads better than if len(items) > 0: and does the same thing.

The one place to avoid it is when zero is a real value. if score: treats a score of 0 the same as a missing score. When you mean "was a value provided", write if score is not None:.

The or trap

x = 3
if x == 1 or 2:
    print("matched")
matched

x is 3, and the branch still runs. Python does not read this as "x equals 1 or 2". It reads (x == 1) or 2. The first half is False, so or returns its second half, the number 2, which is truthy. Measured, the expression x == 1 or 2 evaluates to 2, so this condition is true for every value of x.

The fix is to repeat the comparison, x == 1 or x == 2, or better, test membership:

print(x in (1, 2))   # False

Combining conditions: and, or, not, and chains

Python lets you chain comparisons the way you would write them in maths:

age = 25
if 18 <= age < 65:
    print("working age")

That is 18 <= age and age < 65, written once. Chains also allow things like 1 < 3 > 2, which is legal and true and should never appear in code anyone else reads.

and and or stop as soon as they know the answer, so the order of the two halves can protect you:

user = None
if user and user["admin"]:
    print("admin")
else:
    print("not an admin")
not an admin

Because user is None, and stops before it ever evaluates user["admin"]. Swap the halves and Python raises TypeError: 'NoneType' object is not subscriptable, the error explained in fixing NoneType errors.

Conditions on strings, lists and dictionaries

in is one of the most useful things to put in an if. Its meaning depends on what is on the right-hand side:

email = "ada@example.com"
print("@" in email)              # True: a substring check
print(email.endswith(".com"))    # True

stock = {"apple": 3}
print("apple" in stock)          # True: checks the keys
print(3 in stock)                # False: values are not searched

On a string, in looks for a substring. On a list, it looks for a matching item. On a dictionary, it checks the keys only, which is why 3 in stock is false even though 3 is right there as a value. Use 3 in stock.values() for that.

String comparisons are case-sensitive, so "Yes" == "yes" is False. When you compare text that a person typed, lowercase it first: answer.lower() == "yes". The strip() method is worth adding too, since a trailing space makes two otherwise identical strings unequal.

is None, not == None

Use is to check for None, as PEP 8 recommends. is checks that two names point to the same object, and there is only ever one None. == asks the object whether it considers itself equal, and an object can answer anything:

class Weird:
    def __eq__(self, other):
        return True

print(Weird() == None)   # True
print(Weird() is None)   # False

That class is deliberately silly, but real libraries override ==. NumPy arrays and database query builders both do, and == None on them does not give you a simple yes or no.

Nested ifs vs early returns

Deeply nested conditions are hard to follow:

def shipping(order):
    if order["items"]:
        if order["country"] == "UK":
            if order["total"] >= 50:
                return "free UK delivery"
            return "standard UK delivery"
        return "international"
    return "nothing to ship"

Inside a function, you can flatten them by handling the exceptional cases first and returning early. These are often called guard clauses:

def shipping(order):
    if not order["items"]:
        return "nothing to ship"
    if order["country"] != "UK":
        return "international"
    if order["total"] >= 50:
        return "free UK delivery"
    return "standard UK delivery"

print(shipping({"items": [1], "country": "UK", "total": 72}))   # free UK delivery

Same behaviour, no nesting, and each rule reads as one line. Outside a function, combining conditions with and does a similar job.

One-line if: the conditional expression

When you are choosing between two values rather than running two blocks, Python has a one-line form:

age = 20
status = "adult" if age >= 18 else "minor"
print(status)   # adult

Use it for values. Once either side needs more than an expression, write a normal if.

all() and any() turn a whole list of conditions into one: all(checks) is true when every item is truthy, any(checks) when at least one is. On an empty list, all([]) is True and any([]) is False. "Every one of zero checks passed" is technically true, which can wave through a validation step that had nothing to validate.

match-case: Python's switch statement

Python 3.10 added match, which compares one value against several patterns:

def describe(code):
    match code:
        case 200:
            return "ok"
        case 404 | 410:
            return "gone"
        case _:
            return "other"

print(describe(410))   # gone

| means "or" inside a pattern, and _ matches anything, like else. For plain value lookups like this one, a dictionary with .get(code, "other") is often shorter.

There is a trap that if does not have. A bare name in a case does not compare against a variable; it captures the value into that name:

OK = 200

def check(code):
    match code:
        case OK:
            return "ok"

print(check(404))   # ok

case OK: matched 404, because it means "match anything and call it OK". With any case after it, Python refuses to compile: SyntaxError: name capture 'OK' makes remaining patterns unreachable. With nothing after it, as here, it silently matches everything. To compare against a constant, use a dotted name such as Status.OK or a literal.

Quick reference

if x > 10:                     # checked first
    ...
elif x > 5:                    # only if the first was false
    ...
else:                          # only if nothing matched
    ...

if items:                      # non-empty
if value is not None:          # when 0 or "" are valid values
if x in (1, 2):                # never: if x == 1 or 2
if 0 <= x < 10:                # chained comparison
label = "a" if cond else "b"   # choose a value

Want to practise conditionals in lessons where the Python runs in your browser? Start the Python track.

Keep reading

More from the blog

White text reading 'What if name equals main does in Python' on a dark purple dotted background
October 27, 2026·8 min readLearn

What if __name__ == '__main__' actually does in Python

In Python, if __name__ == '__main__' runs code only when you run the file directly, not when it is imported. What __name__ holds and when you need it.

Read more
White text reading 'How to reverse a list in Python' on a dark purple dotted background
October 26, 2026·8 min readLearn

How to reverse a list in Python (and a string)

To reverse a list in Python, use reverse() to change it in place, [::-1] for a reversed copy, or reversed() to loop backwards. Which to use and why.

Read more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentReact DeveloperPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs Mimo
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs Sololearn
Devpuff vs freeCodeCamp
Read All Comparisons