You’ve used for x in something: to walk over lists, strings, dictionaries, and range(...). They all work the same way because they’re all iterables — objects Python knows how to walk over.

This lesson digs a little deeper into range, then explains what “iterable” really means.

range() in detail

range() produces a sequence of numbers without storing them all in memory at once. There are three forms:

range(stop)               # 0, 1, 2, ..., stop-1
range(start, stop)        # start, start+1, ..., stop-1
range(start, stop, step)  # start, start+step, ..., < stop

A few examples:

list(range(5))             # [0, 1, 2, 3, 4]
list(range(2, 8))          # [2, 3, 4, 5, 6, 7]
list(range(0, 20, 3))      # [0, 3, 6, 9, 12, 15, 18]
list(range(10, 0, -1))     # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Wrapping a range() in list() shows what it produces. The range itself doesn’t allocate a list — it generates values on demand.

The stop value is always exclusive:

range(1, 5)   # 1, 2, 3, 4 — no 5

That feels odd at first, but it has a nice property: range(n) produces exactly n values.

Iterables — the unifying idea

An iterable is anything you can put after in in a for loop. The most common iterables in Python:

# strings
for c in "Python":
    ...

# lists
for n in [1, 2, 3]:
    ...

# tuples
for x in (10, 20, 30):
    ...

# sets
for s in {"a", "b", "c"}:
    ...

# dictionary keys
for key in {"a": 1, "b": 2}:
    ...

# range
for i in range(5):
    ...

# file lines
for line in open("data.txt"):
    ...

All of these work the same way: the for statement asks the object “give me your next value”, over and over, until there isn’t one.

We’ll cover this protocol formally in Section 8 (Iterators and Generators) — for now, it’s enough to know that any iterable can power a for loop.

Useful built-ins that work on iterables

Most of Python’s built-in functions accept any iterable, not just lists:

nums = range(1, 11)

print(sum(nums))     # 55
print(min(nums))     # 1
print(max(nums))     # 10
print(len(range(10)))  # 10
print(list(nums))    # [1, 2, ..., 10]

You can also do:

words: list[str] = ["python", "go", "rust"]

print(sorted(words))         # ['go', 'python', 'rust']
print(", ".join(words))      # 'python, go, rust'

", ".join(iterable) is the standard way to combine strings — much faster than building one with + in a loop.

Nested loops

A loop can sit inside another loop. The inner loop runs in full for each iteration of the outer one:

for i in range(1, 4):
    for j in range(1, 4):
        print(f"{i} × {j} = {i * j}")
    print("---")
1 × 1 = 1
1 × 2 = 2
1 × 3 = 3
---
2 × 1 = 2
2 × 2 = 4
2 × 3 = 6
---
3 × 1 = 3
3 × 2 = 6
3 × 3 = 9
---

Nested loops are perfect for grids, tables, and pairs. Try to avoid more than two levels of nesting — beyond that, code becomes hard to follow and there’s usually a cleaner approach.

A small example — find the pair of numbers in a list that adds up to a target:

numbers: list[int] = [3, 7, 1, 9, 4]
target: int = 10

for i in range(len(numbers)):
    for j in range(i + 1, len(numbers)):
        if numbers[i] + numbers[j] == target:
            print(f"{numbers[i]} + {numbers[j]} = {target}")
3 + 7 = 10
1 + 9 = 10

What’s next

You can iterate over anything Python considers iterable. Last lesson of this section: pattern matching with match/case — modern Python’s switch statement.

Toggle theme (T)