# Loops

Course: [Python](https://www.devtutorials.ai/learn/python) · Section: Control Flow
Source: https://www.devtutorials.ai/learn/python/control-flow/loops

> Repeating work with for and while, counting with range(), break, continue and pass, nested loops, avoiding infinite loops, and the loop else.

Imagine marking thirty exam papers.

You pick up a paper, add up the marks, write the total on the front, and put it on the pile. Then you pick up the next one and do exactly the same thing. Thirty times.

The job is one set of steps, repeated once for each paper. You do not write out thirty separate sets of instructions.

Programs repeat work the same way, and the tool is called a **loop**.

```python runnable title="The same work, once for each mark"
marks = [78, 85, 60]

for mark in marks:
    print("Mark:", mark, "- Passed:", mark >= 35)
```

Two lines of code did the work three times. Add a fourth mark to the list and run it again — the loop handles it without a single change.

## What You Will Learn

In this lesson, you will learn:

- How to repeat work once for each item in a list
- How to count with `range()`, including counting backwards
- How to repeat while something stays true
- How to leave a loop early, skip one turn, or do nothing
- How to put one loop inside another
- What an infinite loop is and how to avoid writing one
- The `else` that can follow a loop, and when it runs

A loop runs the same block of code once for each item, or until a test stops being true.

## Introduction to Loops

### What Are Loops?

A **loop** repeats a block of code. Each repeat is called an **iteration**.

You will also hear the word **turn** used in this lesson. A turn and an iteration are the same thing: one pass through the loop body.

### Why Loops Are Used

Without a loop, repeated work has to be written out in full.

```python runnable title="Three marks, written out three times"
marks = [78, 85, 60]

print(marks[0])
print(marks[1])
print(marks[2])
```

That works for three marks. For thirty it is unreadable, and for a list whose length you do not know in advance it is impossible.

```python runnable title="Any number of marks, one block of code"
marks = [78, 85, 60, 92, 45, 67, 88]

for mark in marks:
    print(mark)
```

The same two lines handle seven marks, or seven hundred.

### Types of Loops in Python

Python has two.

<div className="tscroll">

| Loop | Use it when | Stops when |
|---|---|---|
| `for` | you know what you are going through — a list, a range, some text | the items run out |
| `while` | you do not know how many turns it will take | the condition becomes false |

</div>

A rough rule: counting or going through a collection is a `for`. Waiting for something to change is a `while`.

You will meet both loop types in this lesson, starting with `for`.

## for Loop

### What Is a for Loop?

A `for` loop takes each item of a collection in turn and runs its block once for each.

```text
for variable in collection:
    code that runs once per item
```

The line ends in a colon, and the block is indented, exactly like an `if`.

```python runnable title="One turn for each item"
subjects = ["Maths", "Science", "English"]

for subject in subjects:
    print("Marking", subject)

print("All subjects marked")
```

The last line is not indented, so it runs once, after the loop has finished.

### Iterating Over Sequences

A `for` loop works on anything Python can take items from one at a time.

```python runnable title="A list, some text, and a pair"
for mark in [78, 85]:
    print("Mark:", mark)

for letter in "Asha":
    print("Letter:", letter)

for value in ("maths", 78):
    print("Value:", value)
```

Text gives you one character at a time, which is often a surprise the first time you see it.

A dictionary gives you its keys:

```python runnable title="Going through a dictionary"
report = {"maths": 78, "science": 85}

for subject in report:
    print(subject, "->", report[subject])
```

`.items()` gives you the key and the value together, which usually reads better:

```python runnable title="Keys and values together"
report = {"maths": 78, "science": 85}

for subject, mark in report.items():
    print(subject, "->", mark)
```

### Loop Variables

The name after `for` is the **loop variable**. It holds one item at a time, and Python gives it a new value on every turn.

You choose the name. Choose one that says what the item is.

```python runnable title="A name that explains itself"
marks = [78, 85, 60]

for mark in marks:
    print(mark, "out of 100")
```

`for mark in marks` reads as a sentence. `for m in marks` does not.

The loop variable still exists after the loop ends, holding the last item:

```python runnable title="The variable outlives the loop"
marks = [78, 85, 60]

for mark in marks:
    pass

print("Last mark:", mark)
```

That is occasionally useful and more often a source of confusion, so do not rely on it.

## range() Function

### Creating Ranges

`range()` produces a run of whole numbers. It is what you use when you want to count rather than go through a collection.

```python runnable title="Counting from zero"
for number in range(5):
    print(number)
```

`range(5)` gives 0, 1, 2, 3, 4. It **starts at 0** and **stops before 5**, so there are exactly five numbers and 5 itself is not one of them.

### Start, Stop, and Step Values

`range()` takes up to three numbers.

```text
range(stop)
range(start, stop)
range(start, stop, step)
```

```python runnable title="All three forms"
print(list(range(5)))
print(list(range(1, 6)))
print(list(range(0, 10, 2)))
```

`list()` is used here only so the numbers can be printed in one go. In a loop you do not need it.

The stop value is never included. `range(1, 6)` ends at 5.

A negative step counts backwards:

```python runnable title="Counting down"
for number in range(5, 0, -1):
    print(number)

print("Lift off")
```

Getting the direction wrong gives an empty range rather than an error:

```python runnable title="A range that produces nothing"
print(list(range(5, 0)))

for number in range(5, 0):
    print("This never prints")

print("The loop body never ran")
```

`range(5, 0)` counts up from 5 towards 0, which it can never reach, so it produces nothing at all. The loop runs zero times and the program carries on with no complaint.

### Using range() with Loops

Counting from 1 is the usual reason to give a start value:

```python runnable title="Numbering the subjects"
subjects = ["Maths", "Science", "English"]

for number in range(1, len(subjects) + 1):
    print(number, subjects[number - 1])
```

That works, but Python has a neater way when you need the position and the item together:

```python runnable title="enumerate gives position and item"
subjects = ["Maths", "Science", "English"]

for number, subject in enumerate(subjects, start=1):
    print(number, subject)
```

No arithmetic on the index, so no chance of an off-by-one mistake.

Use `range()` when you want numbers. Use the collection itself when you want its items, and `enumerate()` when you want both.

## while Loop

### What Is a while Loop?

A `while` loop repeats as long as its condition stays true.

```text
while condition:
    code that runs while the condition is true
```

```python runnable title="Repeating while something is true"
lives = 3

while lives > 0:
    print("Lives left:", lives)
    lives -= 1

print("Game over")
```

The condition is checked **before** each turn. When `lives` reaches 0 the test fails and the loop ends.

If the condition is false at the start, the body never runs at all:

```python runnable title="A loop that never starts"
lives = 0

while lives > 0:
    print("This never prints")

print("Finished without looping")
```

### Loop Conditions

The condition is any expression that gives `True` or `False`, exactly as in an `if`.

```python runnable title="Counting up to a total"
total = 0
marks = 0

while total < 200:
    marks += 50
    total += marks
    print("Added", marks, "- total now", total)

print("Final total:", total)
```

### Updating Conditions

**Something inside the loop must change what the condition depends on.** This is the whole difference between a loop that ends and one that does not.

```python runnable title="The line that makes the loop end"
countdown = 3

while countdown > 0:
    print(countdown)
    countdown -= 1

print("Done")
```

Take out `countdown -= 1` and `countdown` stays 3 for ever. The condition stays true, and the loop never stops.

A safe habit when you write a `while`: write the line that changes the condition at the same moment you write the condition itself, before you write anything else in the body.

## Loop Control Statements

### break

`break` leaves the loop immediately. Nothing after it in the body runs, and no further turns happen.

```python runnable title="Stopping as soon as you find it"
marks = [78, 85, 20, 60]

for mark in marks:
    if mark < 35:
        print("Found a failing mark:", mark)
        break
    print("Checked", mark)

print("Search finished")
```

The loop stopped at 20 and never looked at 60.

`break` is how you stop as soon as you have your answer, instead of walking through the rest of a list for nothing.

### continue

`continue` skips the rest of this turn and moves on to the next one. The loop itself keeps going.

```python runnable title="Skipping one turn"
marks = [78, 85, 20, 60]

for mark in marks:
    if mark < 35:
        continue
    print("Passed:", mark)

print("Finished")
```

The failing mark was skipped, and the loop carried on to 60.

<div className="tscroll">

| Statement | What it does |
|---|---|
| `break` | leaves the loop completely |
| `continue` | skips the rest of this turn, starts the next |
| `pass` | does nothing at all |

</div>

### pass

`pass` does nothing. It exists because Python needs a block after a colon, and sometimes you have nothing to put there yet.

```python runnable title="A placeholder that keeps the program running"
marks = [78, 85, 60]

for mark in marks:
    if mark >= 80:
        pass
    else:
        print(mark, "is below 80")
```

Without `pass`, the empty `if` block would stop the program with an `IndentationError`.

Use it as a reminder of a branch you intend to fill in. It is not a way to skip a turn — that is `continue`.

## Nested Loops

### Nested for Loops

A loop inside a loop is a **nested loop**. The inner loop runs all the way through on every single turn of the outer one.

```python runnable title="Every subject for every student"
students = ["Asha", "Ravi"]
subjects = ["Maths", "Science"]

for student in students:
    for subject in subjects:
        print(student, "-", subject)
```

Two students and two subjects give four lines. The counts multiply: ten students and ten subjects would be a hundred.

```python runnable title="A table built from two loops"
for row in range(1, 4):
    line = ""
    for column in range(1, 4):
        line += str(row * column) + "\t"
    print(line)
```

The inner loop builds one row. The outer loop moves to the next row.

### Nested while Loops

The same idea works with `while`, and now **both** counters have to be updated.

```python runnable title="Two counters, both reset and both updated"
row = 1

while row <= 3:
    column = 1
    while column <= 3:
        print(row, column, end="  ")
        column += 1
    print()
    row += 1
```

Notice `column = 1` sits inside the outer loop. It has to be reset at the start of every row, or the inner loop would run only once and then find its condition already false.

A nested loop multiplies the work. Two lists of 1,000 items each give a million turns. Before nesting, ask whether one pass would do.

## Infinite Loops

### What Is an Infinite Loop?

An **infinite loop** is a loop whose condition never becomes false, so it never ends.

In a browser page like this one, an infinite loop freezes the tab. On your own computer you stop it with Ctrl+C. Either way, nothing else happens until you do.

So the example below is deliberately **not** runnable — reading it is enough:

```python
lives = 3

while lives > 0:
    print(lives)
```

`lives` is never changed, so `lives > 0` is true for ever.

### Common Causes

<div className="tscroll">

| Cause | Example | Fix |
|---|---|---|
| Nothing updates the condition | `while lives > 0:` with no `lives -= 1` | change the value inside the loop |
| Updating in the wrong direction | `countdown += 1` when counting down to zero | `countdown -= 1` |
| The update is skipped by a `continue` | `continue` placed before the counter changes | update the counter before the `continue` |
| The update sits outside the loop | the counter line is not indented | indent it into the loop body |
| A condition that cannot be reached | `while total != 10:` adding 3 each time | use `>=` or `<=` rather than `!=` |

The last one is the sneaky one. Counting 0, 3, 6, 9, 12 skips straight past 10, so `total != 10` is true for ever.

</div>

### Preventing Infinite Loops

Three habits deal with almost all of them.

**Write the update with the condition.** The moment you write `while countdown > 0:`, write `countdown -= 1` before anything else.

**Prefer `for` when you can.** A `for` loop over a list or a range ends by itself, because the items run out.

```python runnable title="A loop that cannot run for ever"
for countdown in range(3, 0, -1):
    print(countdown)

print("Done")
```

**Give an open-ended loop a limit.** When a loop waits for something that might never happen, count the turns and stop at a sensible number.

```python runnable title="A guard that stops a runaway"
MAX_TRIES = 5

total = 0
tries = 0

while total != 10 and tries < MAX_TRIES:
    total += 3
    tries += 1
    print("Try", tries, "- total", total)

if total != 10:
    print("Gave up after", tries, "tries")
```

The condition `total != 10` can never be satisfied by adding 3 at a time. Without the guard this loop would run for ever; with it, the program says so and moves on.

In a `while` loop, prefer `<` or `>` over `!=`. A counter can step straight past an exact value and never come back to it.

## Loop else Clause

### for-else

A loop can have an `else`. It runs when the loop finished normally — that is, **when no `break` happened**.

```python runnable title="The else runs when nothing broke out"
marks = [78, 85, 60]

for mark in marks:
    if mark < 35:
        print("Found a failing mark")
        break
else:
    print("Every mark passed")
```

No mark was below 35, so the loop ran to the end and the `else` ran.

### while-else

The same rule applies to a `while`.

```python runnable title="A while loop with an else"
countdown = 3

while countdown > 0:
    print(countdown)
    countdown -= 1
else:
    print("Countdown finished without interruption")
```

### When else Executes

This is the part people get wrong, so it is worth stating plainly.

**The `else` of a loop runs when the loop ends on its own. It does not run if a `break` stopped it.**

It is nothing to do with the loop body being empty, and nothing to do with the condition being false at the start.

```python runnable title="A break skips the else"
marks = [78, 20, 60]

for mark in marks:
    if mark < 35:
        print("Found a failing mark:", mark)
        break
else:
    print("Every mark passed")

print("Checked")
```

The `break` fired, so the `else` was skipped and only `Checked` followed.

A loop that never runs at all still counts as finishing normally:

```python runnable title="No turns at all, and the else still runs"
for mark in []:
    print("This never prints")
else:
    print("The else still runs")
```

The honest summary: read `else` here as **"if no break"**. The keyword is a poor name, and even experienced Python programmers avoid it for that reason. The same job is often clearer with a flag or a function.

## Common mistakes

Each row is one mistake. The middle column is what you will actually see on screen.

<div className="tscroll">

| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| Nothing updates the `while` condition | The page or terminal freezes | The condition stays true for ever | Change the value inside the loop body |
| The update sits outside the loop | The loop never ends | The line is not indented into the body | Indent it into the loop |
| Expecting `range(5)` to include 5 | Four turns where you wanted five | `range()` stops before the stop value | `range(1, 6)` for 1 to 5 |
| `range(5, 0)` with no step | The loop body never runs, and no error | Counting up from 5 can never reach 0 | `range(5, 0, -1)` |
| Using `!=` in a counting condition | The loop never ends | The counter steps straight past the value | Use `<` or `>` instead |
| Forgetting to reset the inner counter | The inner loop runs only once | Its condition is already false after the first row | Reset it inside the outer loop |
| `continue` placed before the update | The loop never ends | The line that changes the condition is skipped | Update the counter before the `continue` |
| Expecting `pass` to skip a turn | Nothing is skipped | `pass` does nothing at all | Use `continue` |
| Expecting the loop `else` to always run | A message that disappears sometimes | `else` is skipped whenever a `break` fires | Read it as "if no break" |
| Changing a list while looping over it | Items quietly skipped | The positions shift under the loop | Loop over a copy, or build a new list |
| Forgetting the colon | `SyntaxError: expected ':'` | `for` and `while` both open a block | End the line with `:` |

</div>

## What to Remember

- **A loop runs the same block of code once for each item, or until a test stops being true.**
- A `for` loop goes through a collection and ends when the items run out.
- A `while` loop repeats while its condition is true, and checks the condition before each turn.
- `range(stop)` starts at 0. `range(start, stop)` never includes the stop value. A negative step counts backwards.
- `enumerate()` gives the position and the item together, with no arithmetic on the index.
- `break` leaves the loop. `continue` skips the rest of this turn. `pass` does nothing.
- A nested loop runs the inner loop fully on every turn of the outer one, so the work multiplies.
- **Something inside a `while` loop must change what the condition depends on**, or it never ends.
- Prefer `<` or `>` to `!=` in a counting condition, and give an open-ended loop a limit.
- The `else` after a loop means **"if no break"**, and it runs even when the loop had no turns at all.

Next: **Functions**
