Using Loops to Iterate Over Data

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.

ToolPurpose
for
statement
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 comprehension
expression
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}