# 🤯TIL for...else exists in Python

Today, I learned that you can pair an `else` clause with a loop statement such as `for` or `while`. It looks like this:

```python
for i in iterator:
    # do something
else:
    # do something else
```

This may look quite counterintuitive for the first time, but we will unwrap it in a moment! ✨

First, let's review the basic syntax of a `for` loop in Python. A `for` loop allows you to iterate over an iterator (such as a list, tuple, or string) and execute a block of code for each value. Here's an example:

```python
fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
    print(fruit)
```

This code would output:

```python
apple
banana
cherry
```

# Simple example

Now, let's add an `else` clause to this loop:

```python
fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
    print(fruit)
else:
    print("No more fruits")
```

This code would output:

```python
apple
banana
cherry
No more fruits
```

In this case, the `else` block is executed after the `for` loop has finished normally. If the loop is exited early (for example, by a `break` statement), the `else` block will not be executed.

# Linear search example

This `for..else` can be quite helpful in some scenarios. For example, you can use it to check if a value exists in a list:

```python
values = [1, 2, 3, 4, 5]
for value in values:
    if value == 6:
        print("Value found!")
        break
else:
    print("Value not found.")
```

Let's take a look at another version without using `for...else`:

```python
values = [1, 2, 3, 4, 5]

found = False
for value in values:
    if value == 6:
        found = True
        break

if found:
    print("Value found!")
else:
    print("Value not found.")
```

You can see, we have to introduce a boolean flag if we do not use the `else` clause. This makes the control flow more complex.

# Breaking out of nested loops

In Python, the `break` statement only terminates the nearest enclosing loop. In some cases, you may want to break out of a parent loop.

Consider this code that determines if `n` is a prime number:

```python
n = 11

if n <= 1:
    print(f"{n} is not a prime number!")
    exit()

for i in range(2, n):
    for j in range(2, n):
        if i * j == n:
            print(f"{n} is not a prime number!")
            break
    else:
        continue # only executed if the inner loop did NOT break
    break # only executed if the inner loop DID break
else:
    print(f"{n} is a prime number!")
```

It works by iterating over all pairs of numbers `(i, j)` less than `n` and checking if the product of any of them is equal to `n`. In the case where we find `i * j` is equal to `n`, we can say that `n` is not a prime number and we would want to exit the outer `for` loop early.

If the inner loop completes successfully (i.e., without breaking), it means that no pairs of numbers were found whose product equals `n`. In this case, the `else` clause of the inner loop is executed. This `else` clause triggers the `continue` statement, which causes the outer loop to immediately proceed to the next value of `i` without executing the remaining code in the outer loop.

If the inner loop breaks at any point, the `else` clause of the inner loop is skipped and the outer loop would reach the `break` statement, which breaks the outer loop as well.

[PEP 3136](https://peps.python.org/pep-3136/#specification) is a Python Enhancement Proposal that proposes support for labels in `break` and `continue` statements. However, it was [rejected](https://mail.python.org/pipermail/python-3000/2007-July/008663.html) due to the complexity it would add to Python.

The example I have shown above is one of the workarounds that produce clean code. Another solution is to put the code in a function and use `return` instead. Using a function is usually preferred because code with low cyclomatic complexity is usually more maintainable.
