Most programs work by applying the same operation to many pieces of data, and the for loop is Python's tool for that job. A for loop walks across any iterable — a list, tuple, string, dictionary, set, file, range, generator, or custom class that implements __iter__ — binding each element to a name and running the indented block once per element. Unlike C-style for loops, you never manage indices by hand; iteration is the common operation.
Iteration works the same regardless of source, which is why Python code tends to look consistent: the pattern for x in <source>: reads the same for ten items or ten million. A string yields characters, a file yields lines, a dictionary yields keys (use .items() for pairs, .values() for values), a range yields integers, and a generator yields whatever the generator produces. When you write a for loop, describe the items in the name: for line in log_file, for user in roster, for row in rows.
The three iteration helpers you will reach for most often are range(), enumerate() and zip(). range(start, stop, step) produces a lazy sequence of integers — ideal for counted iteration (for i in range(10):). enumerate() pairs each item with its index (for i, item in enumerate(items):), which beats manual counters every time. zip() walks two or more iterables in parallel (for name, score in zip(names, scores):); from Python 3.10 you can add strict=True to make unequal lengths a loud error.
For common cases where the loop builds a new collection, comprehensions are shorter and usually faster. [func(x) for x in items] replaces a whole loop-and-append block with a single line; {x for x in items} builds a set, {k: v for k, v in pairs} builds a dict, and (x for x in items) is a lazy generator. The rule of thumb is: use a comprehension when the loop body is a single expression; stay with a plain for when there are multiple statements or side effects.
Iteration is lazy wherever possible. range, zip, enumerate, map, filter, dictionary views, and generator expressions do not materialise a whole list in memory; they hand out one element at a time. This is why you can iterate a 10 GB log file with for line in open(...) without loading it all. When you need every item in memory (for random access or multiple passes) wrap it in list(...); otherwise prefer the lazy form.
Iterating dictionaries, files, and parallel sequences
A plain for k in d: iterates dictionary keys, which is concise but discards the values. Use for k, v in d.items(): to get both at once. Files are iterated line by line (each line keeps its trailing newline — call .rstrip() if you want to drop it). For parallel iteration, zip() stops at the shortest iterable unless you pass strict=True.
Comprehensions vs explicit loops
Comprehensions support an optional filter: [x**2 for x in items if x > 0]. Nested loops map to nested for clauses inside the comprehension. For three-level-deep logic, stop and use a regular loop instead; readability trumps brevity every time.
These helpers turn up in almost every for loop you will ever write.
| Tool | Purpose |
|---|---|
forstatement | Walks through every element of an iterable. |
range()built-in | Lazy sequence of integers for counted loops. |
enumerate()built-in | Yields (index, item) pairs during iteration. |
zip()built-in | Walks multiple iterables in parallel; strict=True (3.10+). |
.items() / .keys() / .values()dict methods | Iterate over key/value pairs, keys, or values. |
list comprehensionexpression | Builds a new list by looping and filtering in one line. |
sorted()built-in | Returns a new sorted list; ideal at the head of a loop. |
reversed()built-in | Iterates any indexed sequence from the end backwards. |
Using Loops to Iterate Over Data code example
The example processes a small CSV-like table in memory: it walks rows, indexes them, zips parallel columns, and builds a summary with a dict comprehension.
# Lesson: Using Loops to Iterate Over Data
# Goal: touch every element of a collection cleanly using for, enumerate, zip,
# dict.items and a comprehension.
from statistics import mean
headers = ["name", "team", "points"]
rows = [
["Ada", "alpha", 12],
["Ben", "beta", 8],
["Cho", "alpha", 17],
["Dara", "beta", 11],
]
# 1) enumerate for one-based row numbers
for idx, row in enumerate(rows, start=1):
print(f"row {idx}: {row}")
# 2) zip headers with each row to build labelled dicts
records = [dict(zip(headers, row)) for row in rows]
# 3) iterate dict.items() when you need both key and value
for rec in records:
for key, value in rec.items():
print(f" {key:>6} = {value}")
print(" --")
# 4) group the records by team with a plain loop (multi-line body)
teams: dict[str, list[int]] = {}
for rec in records:
teams.setdefault(rec["team"], []).append(rec["points"])
# 5) a dict comprehension turns the grouped values into averages
summary = {team: mean(points) for team, points in teams.items()}
print("averages:", summary)
Five distinct iteration patterns in one file:
1) enumerate(..., start=1) numbers rows without a manual counter.
2) zip(headers, row) inside a comprehension produces labelled dicts.
3) dict.items() yields (key, value) pairs for reporting.
4) setdefault() is the idiomatic way to group values by key.
5) A dict comprehension compresses a map-step into one line.
Two short snippets to stretch the patterns.
# Example A: iterate a file line by line
# with open("notes.txt") as f:
# for lineno, line in enumerate(f, start=1):
# print(f"{lineno:03d} {line.rstrip()}")
# Example B: two-sequence iteration with zip(strict=True)
names = ["Ada", "Ben", "Cho"]
scores = [9.5, 7.0, 8.75]
for name, score in zip(names, scores, strict=True):
print(f"{name:<4} {score:.2f}")
# If the two lists had different lengths, strict=True raises ValueError
# rather than silently truncating, which is almost always what you want.
Guarantees that small comprehensions behave as expected.
assert [x * 2 for x in range(4)] == [0, 2, 4, 6]
assert {x for x in "aabbc"} == {"a", "b", "c"}
assert {k: v for k, v in enumerate("xyz")} == {0: "x", 1: "y", 2: "z"}
assert list(zip([1, 2, 3], "ab")) == [(1, "a"), (2, "b")]
The final print prints the team averages:
row 1: ['Ada', 'alpha', 12]
row 2: ['Ben', 'beta', 8]
row 3: ['Cho', 'alpha', 17]
row 4: ['Dara', 'beta', 11]
(per-record lines omitted for brevity)
averages: {'alpha': 14.5, 'beta': 9.5}