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.