Python Else Statement

21 Nov 2025 · #python

I thought I knew it all.

So when Bruno said, "Throw an else at the end of that for-loop," I thought he was crazy.

Everyone knows else appears in if-statements, not for-loops, right?

Turns out I was wrong. I skeptically placed else after my for-loop... and it worked.

Then began my existential crisis. "What else am I wrong about in Python?"

"What else does else do?" I wondered.

This one's for you Pythonistas who thought else was limited to if-statements.

We'll look at the 4 places Python accepts the else clause.

1. The "if" Statement

No big revelation here: else is used in if-statements.

if something_is_true:
    print("do something")
else:
    print("do something else")

The if-else block is the proverbial fork-in-the-road. If something is true, take path A. If not, take path B.

This is the OG of else clauses.

2. The "for" Loop

News flash: The else clause can appear after a for-loop. Take a look at this example. There are two versions of the for-loop. What do you think the output will be?

Version 1: Standard for-else block

broomsticks = ["Nimbus 2000", "Firebolt", "Comet"]

for broomstick in broomsticks:
    print(broomstick)
else:
    print("HIT THE ELSE STATEMENT")

The output is...

Nimbus 2000
Firebolt
Comet
HIT THE ELSE STATEMENT

Version 2: A for-else block with break

broomsticks = ["Nimbus 2000", "Firebolt", "Comet"]

for broomstick in broomsticks:
    print(broomstick)
    if broomstick == "Firebolt": # check to break
        break
else:
    print("HIT THE ELSE STATEMENT")

But this time, we get...

Nimbus 2000
Firebolt

Did you catch it? The else clause only runs if the loop does not find a break command. Said differently, the else clause only runs if the for-loop is allowed to run to completion.

Both Version 1 and Version 2 step through a list of 3 broomsticks. In each iteration of the loop, the broomstick is printed.

Version 2, however, performs an extra step of checking if the current broomstick is "Firebolt". If so, Python reaches a break command, and the for-loop stops. This is why the else clause of Version 2 never runs.

That's cool, but where would we ever use a for-else block?

One use case is searching for a target. Suppose we need to see if a list of users contains an "admin" user or not.

Let's define our User class:

from dataclasses import dataclass

@dataclass
class User:
    name: str
    is_admin: bool

Now let's search two lists for an admin user:

Search 1: Three users (1 admin)

users = [
    User(name="Harry", is_admin=False),
    User(name="Ron", is_admin=False),
    User(name="Hermione", is_admin=True), # admin!
]
>>> for user in users:
...     if user.is_admin:
...         print(f"Found one admin: {user}")
...         break
... else:
...     print("No admin user found!")
...
Found one admin: User(name='Hermione', is_admin=True)

Search 2: Two users (0 admins)

users = [
    User(name="Harry", is_admin=False),
    User(name="Ron", is_admin=False),
]
>>> for user in users:
...     if user.is_admin:
...         print(f"Found one admin: {user}")
...         break
... else:
...     print("No admin user found!")
...
No admin user found!

Both for-else blocks are the exact same. The users list is the only thing that differs. In Search 2, Python never hits the break statement because we never find a user with is_admin=True. As a result, the for-loop runs to completion, and the else clause is allowed to run, telling us what happened.

Such searches don't require a for-else block. We could create a boolean flag to signal if an admin has been found or not, like this:

admin_found = False # flag to store success or failure

for user in users:
    if user.is_admin:
        print(f"Found one admin: {user}")
        admin_found = True  # update flag - we found admin!
        break

if not admin_found:
    print("No admin user found!")

But this requires an extra variable admin_found and another if-statement to declare the failure. The for-else block performs the same logic more concisely!

3. The "while" Loop

The else clause also appears after while-loops.

The behavior is the same as a for-loop: The else clause only runs if the while-loop runs to completion without a break statement.

Consider an example: We need to connect to a server and retry if our attempts fail.

Here's some boilerplate code to simulate connecting to a server:

import time

def connect_to_server(): # pretend this is more complicated
    return "fail"

We'll use a while-loop to connect to the server. If the attempt fails, we'll wait one second before trying again. After 3 attempts, the while-loop will end.

Consider two versions of this while-loop:

Version 1: The traditional while-loop with boolean flag

attempts_made = 0
success = False # flag to store success or failure

while attempts_made < 3:
    attempts_made += 1
    print(f"Connecting to server (attempt {attempts_made})...")
    if connect_to_server() == "success":
        success = True # update flag
        print("Connected to server!")
        break
    time.sleep(1) # wait 1 second before trying again

if not success:
    raise TimeoutError("Failed to connect to server after 3 attempts")

Output:

Connecting to server (attempt 1)...
Connecting to server (attempt 2)...
Connecting to server (attempt 3)...
Traceback (most recent call last):
  File "<input>", line 9, in <module>
    raise TimeoutError("Failed to connect to server after 3 attempts")
TimeoutError: Failed to connect to server after 3 attempts

Version 2: The while-else approach

attempts_made = 0

while attempts_made < 3:
    attempts_made += 1
    print(f"Connecting to server (attempt {attempts_made})...")
    if connect_to_server() == "success":
        print("Connected to server!")
        break
    time.sleep(1) # wait 1 second before trying again
else:
    raise TimeoutError("Failed to connect to server after 3 attempts")

Output:

Connecting to server (attempt 1)...
Connecting to server (attempt 2)...
Connecting to server (attempt 3)...
Traceback (most recent call last):
  File "<input>", line 9, in <module>
    raise TimeoutError("Failed to connect to server after 3 attempts")
TimeoutError: Failed to connect to server after 3 attempts

The results of both while-loops are the same. After 3 attempts, a TimeoutError is raised. Like the last for-loop example, Version 1 uses a boolean variable and an if-statement to manage the outcome. Version 2's while-else structure clearly separates the successful path (where the loop breaks early) from the failure path (where the loop completes normally).

This comes down to individual preference. Some people prefer the additional variable and if-statement of Version 1. Others like the concise while-else block in Version 2 with two indented paths. You pick whichever you want. I'm not your mother; I'm just giving you options.

This while-else pattern is helpful when we need to wait or retry a task:

  • Wait on a background job to complete
  • Wait on a network resource to be available
  • Retry API calls a limited number of times

4. The "try-except" Block

Sometimes things go wrong. Python's "try-except" block handles failures more gracefully.

A basic try-except block goes like this:

  1. "try" to run this code...
  2. but if you catch an Exception (i.e. the code fails somewhere), run the except clause.

Version 1: The happy path

try:
    print("in try block")
except:
    print("in except block - something went wrong!")

Output:

in try block

In Version 1, the try clause runs successfully with no errors. This is why the except clause is not executed.

Version 2: Something goes wrong

try:
    print("in try clause")
    raise ValueError # simulate an exception
except:
    print("in except clause - something went wrong!")

Output:

in try clause
in except clause - something went wrong!

In Version 2, we simulate an Exception by raising ValueError in the try clause. The interpreter looks for an except clause that catches ValueError. Here, we have a generic except clause that captures all exceptions and prints that something went wrong.

Now let's add a 3rd layer to the try-except block: the else clause! The else clause only runs if no exception is raised in the try clause.

Version 1: The happy path again

try:
    print("in try clause")
except ValueError:
    print("in except clause - reached a ValueError")
else:
    print("HIT THE ELSE STATEMENT - no ValueError")

Output:

in try clause
HIT THE ELSE STATEMENT - no ValueError

Version 2: Something goes wrong again

try:
    print("in try clause")
    raise ValueError # simulate an exception
except ValueError:
    print("in except clause - reached a ValueError")
else:
    print("HIT THE ELSE STATEMENT - no ValueError")

Output:

in try clause
in except clause - reached a ValueError

The else clause is ideal for running follow-up code if the try clause runs successfully. It's useful when we want to separate code that might fail (in try clause) from code that should only run when everything goes well (else clause).

A realistic example involves working with databases. When running a query that would change data, we have the option to commit the change (i.e. save the change) or roll back our change (i.e. undo our work). This a perfect setup for try-except:

import sqlite3

conn = sqlite3.connect("test.db")  # create db connection

try:                               # try to run an insert statement
    cur = conn.execute("INSERT INTO my_table VALUES (?, ?)", (1, "harry"))
except sqlite3.Error as e:         # rollback the changes if an error occurs
    print(f"Something went wrong: {e}")
    conn.rollback()
else:                              # otherwise, commit the changes
    print("Committing database changes")
    conn.commit()
finally:                           # clean up our environment - close connection
    conn.close()

Here the action that might fail is a SQL insert statement, so that's placed in the try clause. After that one of two things happen:

  1. If a SQL error is raised, the except clause runs, which rolls back the transaction
  2. If no SQL error is raised, the else clause runs, which commits the insert statement, effectively saving the database.

Note we added a 4th layer to the try-except block called finally. The finally clause runs at the end of a try-except block, regardless of whether the try clause raised an Exception or not. It can be used to clean up our environment... but that's a post for another day.


There you go! The else statement can be used in exactly 4 locations in Python. Use this cheat sheet to impress your friends:

Control Flow When else Runs Example Use Cases
if ... else Condition in if statement is False Run Path B if Path A is not viable
for ... else Loop completes without break Search for item (and declare not found)
while ... else Loop ends without break Retry or poll for success
try ... else try clause does not raise exception Commit DB transaction

In general, the else clause is skipped if an exception is raised or if Python finds a return, break, or continue statement that causes control to jump away from the else clause.

I have a gripe about else. It's a misleading name, especially in for-loops and while-loops. The word "else" suggests that either the for loop OR the else clause will run (but not both). Instead of "run this loop, OR do that", it's more like "run this loop, THEN do that". Perhaps a better name would've been "then" or "nobreak" or "noexception". Oh well, we're stuck with else.

How do you use else clauses in your code? Call me and let me know... or else.