For, Range, and Iteration Patterns
Key Takeaways
- A for loop assigns the next item from an iterable to the loop variable on each iteration.
- range(stop) starts at 0 and excludes stop; range(start, stop, step) also excludes stop.
- A negative range step counts downward only when start is greater than stop; a zero step raises ValueError.
- After a for loop that ran at least once, the loop variable keeps its last assigned value.
- PCEP range traps usually involve excluded stop values, empty ranges, negative steps, or confusing values with indexes.
What a for loop does
A for loop pulls values from an iterable one at a time. On each pass Python assigns the next value to the loop variable and runs the indented body.
for ch in 'PCEP':
print(ch)
Here ch becomes 'P', 'C', 'E', then 'P'. No manual index is required unless the code genuinely needs positions.
The range function
range() is the most common PCEP loop source. It produces a sequence of integers lazily (it does not build a list).
| Form | Values produced | Note |
|---|---|---|
range(4) | 0, 1, 2, 3 | start defaults to 0 |
range(2, 6) | 2, 3, 4, 5 | stop is excluded |
range(1, 8, 2) | 1, 3, 5, 7 | step is 2 |
range(5, 1, -1) | 5, 4, 3, 2 | counts down |
range(3, 3) | (nothing) | empty range |
The stop value is never included — the single biggest source of off-by-one mistakes on the exam. To loop 1 through 5 inclusive, write range(1, 6).
Negative steps and empty ranges
A negative step moves downward, and start must exceed stop to produce any values.
print(list(range(5, 0, -2))) # [5, 3, 1]
print(list(range(0, 5, -1))) # []
The second range is empty: it begins at 0 and tries to descend toward a higher stop of 5, so it is already past the boundary. A step of 0 raises ValueError because the range could never advance.
Iterating over data versus indexes
For loops iterate directly over strings, lists, and tuples:
letters = ['a', 'b', 'c']
word = ''
for letter in letters:
word += letter.upper()
print(word) # ABC
When you need positions, use range(len(data)):
items = [10, 20, 30]
for i in range(len(items)):
print(i, items[i])
Do not confuse the index with the item. In for item in items, item is 10, 20, 30. In for i in range(len(items)), i is 0, 1, 2 — the position, not the value. PCEP also tests enumerate, which yields index/value pairs: for i, v in enumerate(items) gives (0, 10), (1, 20), (2, 30).
Trace-table method
Write the iteration list first, then trace body variables.
| Source | Loop values |
|---|---|
range(2, 9, 3) | 2, 5, 8 |
'cat' | 'c', 'a', 't' |
[4, 1] | 4, 1 |
| Iteration | n | total before | operation | total after |
|---|---|---|---|---|
| 1 | 2 | 0 | add 2 | 2 |
| 2 | 5 | 2 | add 5 | 7 |
| 3 | 8 | 7 | add 8 | 15 |
Loop variable after the loop
If a for loop runs at least once, the loop variable survives the loop and holds its last value. After for n in range(3):, n equals 2. If the loop never ran (an empty range) and the variable was not defined earlier, reading it afterward raises NameError. PCEP uses this to check whether you noticed an empty range.
Nested for loops
Nested loops multiply iterations: an outer loop of 3 and an inner loop of 2 runs the inner body 6 times. The inner loop completes fully for each single outer value before the outer advances.
Exam traps
Do not assume a loop runs just because it is written; an empty range runs zero times. Do not include the stop value. Do not assume the loop variable resets after the loop. And always read whether the code iterates over values or indexes before predicting the output.
Counting iterations of a range
PCEP often asks how many times a loop body runs rather than what it prints. For range(start, stop, step) with a positive step, the count is (stop - start + step - 1) // step floored at zero, but the reliable exam method is simply to list the values.
| Range | Values | Iterations |
|---|---|---|
range(5) | 0,1,2,3,4 | 5 |
range(2, 10, 2) | 2,4,6,8 | 4 |
range(10, 2, -3) | 10,7,4 | 3 |
range(4, 4) | (none) | 0 |
The last case is the trap: equal start and stop produce an empty range, so the body never runs and a loop variable defined only inside is never created.
Accumulating across a range
Many items combine a range with a running total or product. List the values, then fold them in.
product = 1
for n in range(1, 5):
product *= n
print(product) # 24
The values 1, 2, 3, 4 multiply to 24 (factorial of 4). Starting product at 0 instead of 1 would wrongly give 0, a deliberate distractor.
Iterating a string by index versus by character
name = 'abc'
for i in range(len(name)):
print(i, name[i])
This prints index/character pairs 0 a, 1 b, 2 c. Compare with for c in name:, which yields only the characters. Confusing these is one of the most tested distinctions in this domain.
Reading checklist for for/range questions
- Write out the exact iteration values before anything else.
- Confirm whether the loop iterates over values, characters, or indexes.
- Check for an empty range (equal bounds, or a step pointing the wrong way).
- Trace each changing variable in its own column.
- Note the loop variable's final value in case the question reads it after the loop.
| Pitfall | Guard |
|---|---|
| Including the stop value | Stop is always excluded |
| Wrong-direction negative step | Start must exceed stop to descend |
| Zero step | Raises ValueError |
| Index vs value confusion | Decide which the loop variable holds first |
With the iteration list written down, for-loop questions reduce to simple arithmetic, and the empty-range and stop-exclusion traps lose their bite.
What is printed by this code? total = 0 for n in range(1, 7, 2): total += n print(total)
Which range produces the values 6, 4, 2?
What is printed by this code? text = 'py' for ch in text: print(ch * 2, end='')
What does enumerate(['a', 'b']) yield on its first iteration when unpacked as for i, v?