PyPython example

How to loop with an index in Python

5 min read▶ Runs in an isolated hosted runtimeUpdated Jul 2026

Quick answer

Wrap the iterable in enumerate() and unpack two names: for i, item in enumerate(items). You get the index and the item, with no manual counter and no indexing back into the list.

Python's for loop iterates over values, not positions — which is why the C-style for i in range(len(items)) feels like it's missing something. It is: enumerate() is the built-in that hands you the counter alongside the value. Each example below runs on this page — hit Run, then edit and run it again.

1enumerate()Recommended

enumerate() yields (index, item) pairs, and the loop target unpacks them into two names. The index starts at 0, matching normal list positions.

loop_index.py

Output

Output: 0 Python, 1 Go, 2 Rust. Nothing is indexed back into the list, so the loop can't drift out of sync with it — and it works on any iterable, not just sequences.

2Start counting at 1

enumerate() takes a second argument, start, which sets the first value of the counter. Use start=1 for anything a human reads — numbered lists, report rows, line numbers — instead of writing index + 1 everywhere.

numbered.py

Output

Prints 1. Write tests through 3. Ship it. start only shifts the counter — it does not skip items, so the whole iterable is still visited.

3range(len(items)) — the C-style habit

This is the loop people carry over from C or Java. It works on a list, but it's strictly worse: you pay an extra items[i] lookup on every line that needs the value, the bounds are yours to get right (len(items) - 1 and + 1 are the classic off-by-one bugs), and it only works on things with a length. Generators, files, zip() objects and map() objects have no len() at all.

range_len.py

Output

The first loop prints the same three lines as enumerate() did; the second prints 0 0, 1 1, 2 4, 3 9. Swap enumerate for range(len(squares)) and you get TypeError: object of type 'generator' has no len(). Reach for range(len(...)) only when you genuinely need positions without the values — say, mutating items[i] in place.

4Two lists at once with zip()

When the "index" you actually wanted was just a way to line up two lists, use zip() — it walks them in lockstep and yields tuples. Need the position as well? Wrap the zip() in enumerate() and unpack the inner tuple in parentheses.

zip_index.py

Output

zip() stops at the shortest input and drops the rest silently — with [1, 2, 3] and [1, 2] you get two pairs, no warning. Pass strict=True to raise ValueError: zip() argument 2 is shorter than argument 1 instead.

5Which should you use?

MethodReadabilityWorks on any iterableBest for
enumerate(items)GreatYesIndex + item, almost always
enumerate(items, start=1)GreatYesHuman-facing numbering
zip(a, b)GreatYesTwo lists in lockstep
enumerate(zip(a, b))GoodYesIndex + both values
range(len(items))VerboseNo — needs len()Positions without values

6Common variation: dicts and filtering

A dict iterates over its keys, so pass d.items() to enumerate() and unpack the pair in parentheses to get counter, key and value at once — insertion order is guaranteed, so the numbering is stable. The same trick keeps the original index when you filter: enumerate() first, then the if, so the surviving items still carry the position they had in the source list.

variations.py

Output

The dict loop prints 1 host localhost, 2 port 9090, 3 debug True, and the comprehension prints [(0, 12), (2, 30), (4, 22)] — note the gaps at 1 and 3, which is exactly the point: filtering first would have renumbered them 0, 1, 2 and lost where they came from.

Frequently asked questions

What is the Pythonic way to get the index in a for loop?

for i, item in enumerate(items). enumerate() is a built-in that yields (index, item) tuples, so you get the counter and the value together without a manual variable or an items[i] lookup.

How do I make enumerate start at 1 instead of 0?

Pass the start argument: for n, item in enumerate(items, start=1). It only shifts the counter — no items are skipped — which makes it the clean way to print numbered lists without writing i + 1.

Why is for i in range(len(items)) considered bad practice?

It needs an extra items[i] lookup for every use of the value, it puts the loop bounds (and the off-by-one bugs) on you, and it only works on objects that have a len(). Generators, files, zip() and map() objects raise TypeError: object of type generator has no len(). Use it only when you want positions without the values, such as assigning to items[i].

How do I loop over two lists at the same time with an index?

Use for i, (a, b) in enumerate(zip(list_a, list_b))zip() pairs the values and enumerate() adds the counter. Note that zip() stops at the shortest list; pass strict=True to raise a ValueError on a length mismatch instead of silently truncating.