PYTHON / LOOPS
enumerate() and tracking the index
Use enumerate() to get a running counter alongside each item, choose its start value, and know when the counter is a real index.
What you will learn
- Unpack enumerate() into two names: for i, item in enumerate(seq)
- Set the first number with enumerate(seq, start=1) for human-facing output
- Know the counter tracks iteration order, not position in the original container
- Use the counter to assign back into a list, never to insert or delete
Understanding enumerate() and tracking the index
enumerate() takes any iterable and returns a new iterator that yields two-element tuples: a counter and the item it just pulled. The loop target `for i, task in enumerate(tasks)` works because Python unpacks each tuple into two names automatically. Nothing about the original object changes; enumerate is a thin wrapper that holds a counter and forwards items one at a time.
That wrapper design is why enumerate() works on things that have no positions at all, such as an open file, a generator, or a set. The consequence is that the number you receive counts how many items enumerate has handed out so far, which only coincides with a list index when you wrapped the list itself. Wrap a filtered generator instead and the numbering becomes 0, 1, 2 over the surviving items, with the original positions lost.
The second argument, `start`, changes the first number and nothing else: `enumerate(tasks, start=1)` still pairs 1 with `tasks[0]`. Treat that number as a label for display, not as an offset, or every `tasks[i]` lookup will be off by one. When you genuinely need to write into a list while walking it, the default 0-based counter from enumerate over that same list is a valid index for replacement.
tasks = ["wash", "dry", "fold"]
for i, task in enumerate(tasks):
print(i, task)
print(list(enumerate(tasks, start=1)))
pair = next(enumerate(tasks))
print(pair, type(pair).__name__)enumerate() wraps an iterable and yields (counter, item) pairs, so the number belongs to the iteration rather than to the container.
Worked examples
Where you put enumerate decides what the number means
Wrapping a filtered generator counts surviving items, while wrapping the original list preserves true positions.
words = ["alpha", "", "beta", "", "gamma"]
for n, w in enumerate((w for w in words if w), start=1):
print(n, w)
print("---")
for pos, w in enumerate(words):
if w:
print(pos, w)Example explained
Line 1The generator drops the empty strings before enumerate sees them, so the counter runs 1, 2, 3 with no gaps.
Line 2In the second loop enumerate wraps `words` itself, so `pos` is a real index into `words` and the skipped items leave gaps 1 and 3.
Line 3Both loops print the same three words; only the numbers differ, which shows the counter is produced by enumerate and not stored on the items.
Using the counter to write back
The 0-based counter from enumerate over a list is a valid index for replacing elements in place.
temps = [12.4, 19.6, 7.25]
for i, t in enumerate(temps):
temps[i] = round(t)
print(temps)
grid = [[1, 2], [3, 4]]
for r, row in enumerate(grid):
for c, val in enumerate(row):
if r == c:
grid[r][c] = 0
print(grid)Example explained
Line 1`temps[i] = round(t)` replaces an element without changing the list length, so the iterator stays in step.
Line 2`t` is a copy of the reference, so rebinding `t` alone would change nothing; the assignment through `temps[i]` is what edits the list.
Line 3Nesting two enumerate calls gives row and column counters, which is how `r == c` picks out the diagonal.
Important notes
enumerate() returns a one-pass iterator, so a second `for` loop over the same enumerate object produces nothing; call enumerate again or build a list from it.
`start` accepts negative and large values and never affects which item is paired with which number; it only shifts the counter.
Common mistakes
Writing `for i in enumerate(tasks)` without unpacking: `i` is the tuple `(0, 'wash')`, so `print(i, tasks[i])` raises TypeError: list indices must be integers or slices, not tuple.
Using `enumerate(seq, start=1)` and then reading `seq[i]`: every lookup is shifted by one, the first item is never seen, and the last iteration raises IndexError.
Calling `seq.pop(i)` or `seq.insert(i, x)` inside the loop: the underlying iterator keeps its own position, so items get skipped or visited twice.
Try it yourself
Change, predict, then run
Given `names = ["Ada", "Grace", "Bo", "Alan"]`, print each name as "1. Ada" through "4. Alan" using a single enumerate loop, and in the same loop record the 0-based position of the first name shorter than four characters, printing it after the loop.
Open the Python workspaceCheck your understanding
With `rows = ['a', '', 'b']`, the loop `for i, r in enumerate(r for r in rows if r): print(i, r)` runs. What is `i` when `r` is `'b'`?
- 1
- 2
- 0
- Nothing prints, because a generator cannot be enumerated
Show answer
enumerate counts the items the generator hands it, and 'b' is the second surviving item, so the counter is 1. The tempting answer 2 is 'b''s index in `rows`, but the generator already discarded the empty string before enumerate saw it, so positional information from `rows` is gone.