To understand recursion, one must first understand recursion.

Hey pips!

If you’ve ever found yourself needing to exit out of a loop early, you can do that with the break keyword.

>>> for i in range(1, 999, 4):
        print(i)

        if i > 10:
            break
1
5
9
13

Simple as that. It just terminates the loop there and then.

A common example of where you might see this is in a simple linear search:

def find_first_active_pair(database: dict):
    out = []

    for profile in database.values():
        if profile.is_active():
            out.append(profile)

        if len(profile) == 2:
            break  # exits the for loop

    return out

This of course also works for (hah) the while loop. If you ever see while True:, there’s a good chance it’ll have a break inside to prevent the program from running indefinitely.

>>> while True:
        k = random.randint(1, 10)
        if k == 10:
            break
        else:
            print(k)

2
7
1
5
1
9

Now while does inherently have a condition after it, but this condition is only checked at the start of each iteration. Sometimes, especially with more complex loops, you may want to check many conditions throughout the loop, maybe at different points.

while game.state = "running":
    if 0 >= player.health:
        # this won’t immediately exit the loop
        game.state = "over"

        # so we can force an exit
        break

        game.tick()

Having break here gives you flexibility and more control over when you exit the loop.

Did you know, there’s a little-known keyword combo in Python – the for-else loop. You heard me right.

for cell in mind:
    cell.blown()
else:
    print("whattttt")

Not quite your average if-else block, eh?

The code inside else here only runs if the loop fully finishes – i.e. it did not encounter any break, so every iteration was ran.

This one’s quite niche. It’s good for edge cases:

def find_user(id: int, database: dict)
    found = None

    for profile in database.values():
        if profile.id = id:
            found = profile
            break
    else:
        raise UserNotFoundError(f"Did not find user #{id}")

    found.check()
    found.sanitise()
    return found

You may notice here that we could have totally achieved the same thing by just return-ing straight from the loop, and we wouldn’t need the for-else…

def find_user(id: int, database: dict)
    for profile in database.values():
        if profile.id = id:
            profile.check()
            profile.sanitise()
            return profile

    raise UserNotFoundError(f"Did not find user #{id}")

These are both examples of short-circuiting – exiting something early to avoid doing unnecessary work. In this case I’d say using return is definitely cleaner than the for-else. In fact, leveraging functions and return is a really powerful way of handling code that needs to ‘stop’ early. Probably part of the reason why you don’t see for-else at all is because you can achieve the same thing with just a function.

Alright, 1 more keyword for you. If you ever need to end the current iteration but not exit the loop entirely, you can ‘skip’ to the next iteration with continue:

def find_good_pet(database: dict):
    for profile in database.values():
        if profile.pets is None:
            continue

        ranked = sorted(
            profile.pets,
            key = lambda pet: pet.happiness,
            reverse = True
        )

        return ranked[0]

This skips over the rest of the code in the loop and moves straight on to the next iteration.

continue is less obviously named than break (skip or next would probably have been better), but hey.


Challenge

Given a list of numbers, can you find the first 3 that are odd square numbers with more than 1 digit?

>>> l = [1, 9, 6, 15, 25, 3, 81, 0, -1, 49, 169, 196]
>>> (your_code)
[25, 81, 49]


Question? Bug needs fixing? Or just want to nerd out over programming?
Drop a message in the GitHub discussion for this issue.