Python for else Loop

One feature that surprises most developers coming from other languages is the Python for else loop. In Python, the else clause is not limited to if statements — you can attach it directly to a for loop, and it gives you a clean, readable way to handle the case where a loop finished without finding what you were looking for. The Python for else loop is one of those features that, once understood, changes how you structure search and validation logic entirely.

What the Python for else Loop Actually Does

When you write an else block after a for loop, that else block runs only if the loop completed all its iterations without hitting a break statement. If the loop exits early because of break, Python skips the else block entirely and continues with whatever comes after.

Think of the else as answering this question: "Did this loop finish on its own, or was it cut short?" If it finished naturally, else runs. If break interrupted it, else does not run.

Here is the simplest demonstration of the Python for else loop:

for number in [10, 20, 30]:
    print(number)
else:
    print("Loop completed without any break")
10
20
30
Loop completed without any break

The loop went through all three items, never encountered a break, so the else block executed after the last iteration was done.

How break Prevents the else Block

The relationship between break and else is the entire foundation of the Python for else loop. The moment Python hits a break inside the for loop, it exits the loop and skips the else block completely — no matter where in the iteration break occurs.

values = [5, 10, 15, 20, 25]

for val in values:
    if val == 15:
        print(f"Found {val}, stopping early")
        break
    print(val)
else:
    print("This line will not print because break ran")
5
10
Found 15, stopping early

The else block was completely bypassed. Python saw break, exited the loop, and jumped past the else. This is the mechanism that makes for else so powerful for search scenarios where you want completely different behavior depending on whether a match was found or not.

Searching Without a Flag Variable

Before the Python for else loop, searching through a list typically required a flag variable to track whether the target was found. The for else pattern removes that need entirely and produces code that is much easier to read.

Here is the old approach using a flag:

fruits = ["mango", "kiwi", "grape", "peach"]
target = "grape"
found = False

for fruit in fruits:
    if fruit == target:
        found = True
        break

if found:
    print(f"{target} was found in the list")
else:
    print(f"{target} was not found in the list")

Now the same logic rewritten using the Python for else loop — the flag variable is gone:

fruits = ["mango", "kiwi", "grape", "peach"]
target = "grape"

for fruit in fruits:
    if fruit == target:
        print(f"{target} was found in the list")
        break
else:
    print(f"{target} was not found in the list")
grape was found in the list

If you change target to something not in the list, the loop exhausts every item without hitting break, and the else block handles the "not found" case. This is the idiomatic Python way to express this pattern according to the official Python docs on compound statements.

Prime Number Detection Using for else

Checking whether a number is prime is one of the most elegant demonstrations of the Python for else loop. A number is prime if no integer between 2 and its square root divides it evenly. You iterate through all possible divisors, and if you find one, you break out — the number is not prime. If the loop exhausts all candidates without finding a divisor, the else block confirms it is prime.

import math

def check_prime(n):
    if n < 2:
        print(f"{n} is not prime")
        return
    for divisor in range(2, int(math.sqrt(n)) + 1):
        if n % divisor == 0:
            print(f"{n} is NOT prime (divisible by {divisor})")
            break
    else:
        print(f"{n} is prime")

check_prime(13)
check_prime(18)
check_prime(2)
13 is prime
18 is NOT prime (divisible by 2)
2 is prime

For 13, the inner loop checked every divisor from 2 to 3 (floor of sqrt(13)) and found nothing, so else announced it as prime. For 18, divisor 2 divided it evenly, break fired, and else was skipped.

The Empty Iterable Edge Case

There is an edge case you should know about: if the iterable given to the for loop is empty, the loop body never executes at all, but the else block still runs. This happens because the loop technically completed without break — it had nothing to iterate over, so no break was ever hit.

empty_list = []

for item in empty_list:
    print(item)
else:
    print("else ran even though the list was empty")
else ran even though the list was empty

This matters in practice. If your else block is meant to say "item not found," and you pass in an empty collection, it will still fire. The else block does not mean "the loop found nothing" — it means "the loop was not interrupted by break." Keep this distinction clearly in mind when designing your logic.

continue Has No Effect on else

Only break affects whether the else block runs. The continue statement skips the rest of the current iteration and moves to the next one, but it does not trigger or prevent the else block in any way. If the loop finishes after using continue in some iterations, else still runs.

scores = [82, 91, 74, 88, 95]
skip_threshold = 80

for score in scores:
    if score < skip_threshold:
        continue
    print(f"Score {score} included")
else:
    print("All iterations processed — loop was not interrupted by break")
Score 82 included
Score 91 included
Score 88 included
Score 95 included
All iterations processed — loop was not interrupted by break

Score 74 was skipped via continue, but the loop kept going through the remaining items. Because break was never called, the else block ran after the final iteration.

Validating That Every Item Meets a Condition

Another practical use of the Python for else loop is validating that every single item in a collection satisfies some condition. If any item fails, you break out immediately. If every item passes, the else block confirms that all checks succeeded.

usernames = ["alice", "charlie", "morgan"]

for name in usernames:
    if len(name) < 4:
        print(f"Username '{name}' is too short — must be at least 4 characters")
        break
else:
    print("All usernames passed the length validation")
All usernames passed the length validation

Now let's test with an invalid entry in the list:

usernames = ["alice", "bob", "morgan"]

for name in usernames:
    if len(name) < 4:
        print(f"Username '{name}' is too short — must be at least 4 characters")
        break
else:
    print("All usernames passed the length validation")
Username 'bob' is too short — must be at least 4 characters

The loop found "bob" failing the condition, broke out, and the else block was never reached. This gives you a clean pass/fail structure with zero extra variables.

Full Working Example

import math


def search_item(collection, target):
    for item in collection:
        if item == target:
            print(f"Found '{target}' in the collection")
            break
    else:
        print(f"'{target}' was not found in the collection")


def is_prime(n):
    if n < 2:
        return False
    for divisor in range(2, int(math.sqrt(n)) + 1):
        if n % divisor == 0:
            break
    else:
        return True
    return False


def all_items_valid(items, min_length):
    for item in items:
        if len(item) < min_length:
            print(f"Validation failed: '{item}' is too short")
            break
    else:
        print("Validation passed: all items meet the minimum length")


# Search examples
inventory = ["wrench", "hammer", "drill", "level"]
search_item(inventory, "drill")
search_item(inventory, "saw")

print()

# Prime number detection
candidates = [11, 15, 17, 21, 29, 35]
for n in candidates:
    label = "prime" if is_prime(n) else "not prime"
    print(f"{n} is {label}")

print()

# Validation examples
valid_tags = ["python", "loops", "coding"]
all_items_valid(valid_tags, 3)

mixed_tags = ["python", "ai", "coding"]
all_items_valid(mixed_tags, 3)

Output

Found 'drill' in the collection
'saw' was not found in the collection

11 is prime
15 is not prime
17 is prime
21 is not prime
29 is prime
35 is not prime

Validation passed: all items meet the minimum length
Validation failed: 'ai' is too short