enumerate()

Getting the position alongside the item, without maintaining a counter or indexing back into the list.

Overview

The two things it replaces

The counter:

i = 0
for name in names:
    print(i, name)
    i += 1

and the index:

for i in range(len(names)):
    print(i, names[i])

Both work. The first has a variable to initialise and remember to increment; forget the increment and the loop runs forever printing zero. The second reads the list twice per pass — once for the length, once per lookup — and puts names[i] where you wanted name.

for i, name in enumerate(names):

says the same thing with neither problem.

enumerate.py

enumerate.py Python 3
Output

                    

enumerate_uses.py

enumerate_uses.py Python 3
Output

                    

Worth knowing

enumerate yields (index, item) tuples; the for unpacks them.
start=1 changes the number it reports, not where it reads from.
It works on any iterable - strings, files, generators - not only lists.
for i in range(len(x)) is the pattern this replaces.

enumerate(): A Practical Guide

enumerate walks a sequence and hands you the position along with the item. It replaces both of the usual workarounds — a counter you increment by hand, and indexing back into the list.

The start argument

for n, line in enumerate(lines, start=1):
    print(f"{n}: {line}")

Line numbers, question numbers, ranked results and anything else a human reads usually start at one. Passing start=1 is better than writing n + 1 inside the body, because it states the intent once at the top rather than repeating an adjustment everywhere the number is used - and it means you cannot apply the adjustment in one place and forget it in another.

It works on anything iterable

enumerate is not a list feature. It works on strings, files, generators, dictionary views and anything else you can loop over, and it does not build a list to do it - it yields pairs as the loop asks for them.

That makes for n, line in enumerate(f) a perfectly good way to number the lines of a file too large to hold in memory, which the range(len(...)) form cannot do at all, because there is no length to take.

Unpacking is doing the work

enumerate yields tuples. Writing for n, item in ... is tuple unpacking, the same feature that makes for k, v in d.items() work. Occasionally you will see it left packed:

for pair in enumerate(items):
    print(pair)      # (0, 'a')

which is legal and rarely what you want. The unpacked form is the point: it gives both halves a name at the top of the loop, so the body never has to say pair[0].

When you do not need it

If you never use the index, do not ask for one. for item in items is the correct loop, and for i, item in enumerate(items) with i unused is noise that a linter will flag. The convention when you need one and not the other is to name the unused variable _.

The off-by-one that start= invites

start=1 changes the number you are handed and nothing else. The item is still the same item, and the position it occupies in the list is still one lower:

names = ["ana", "bo", "cy"]

for n, name in enumerate(names, start=1):
    print(n, name, names[n - 1], names[n] if n < len(names) else "-")
1 ana ana bo
2 bo bo cy
3 cy cy -

The third column is the item, reached correctly with n - 1. The fourth shows what indexing with n gives you: the *next* item, and eventually the end of the list.

This is why start= is for display and not for access. The moment you use the number both to print and to index, you have two meanings for one variable and one of them is wrong. If you genuinely need both, take the real index and add one where you print it:

for i, name in enumerate(names):
    print(i + 1, name, names[i])

Wordier by one character, and it cannot be got wrong.

A worked example: numbering only what matters

The awkward case is numbering output when some items are skipped, because the enumerate counter keeps advancing whether you used it or not:

lines = ["alpha", "", "beta", "", "gamma"]

shown = 0
for line in lines:
    if not line:
        continue
    shown += 1
    print(shown, line)
1 alpha
2 beta
3 gamma

Written with enumerate and a continue, the numbers would have come out 1, 3 and 5, because enumerate counts positions in the input and not lines produced. Neither is wrong; they answer different questions.

The rule that falls out: enumerate numbers the *source*. If you want to number the *output*, and the loop can skip items, you are back to a counter — and that is one of the few remaining places a manual counter is the right answer.

A middle ground worth knowing: filter first, then enumerate the result. for n, line in enumerate((l for l in lines if l), start=1) numbers what survives, and keeps the counter out of the body.

Both numbers at once

Occasionally you want the position *and* the item *and* something from a second sequence, which is where enumerate and zip combine:

names = ["ana", "bo"]
scores = [91, 78]

for i, (name, score) in enumerate(zip(names, scores)):
    print(i, name, score)
0 ana 91
1 bo 78

The brackets around (name, score) are doing real work. enumerate yields (index, item) where the item is the tuple zip produced, so the pattern on the left has to have the same shape: a number, then a pair. Leaving the brackets out is a ValueError about unpacking, and it is one of the clearer error messages you will meet.

The order matters too. zip(enumerate(names), scores) also works and gives you ((0, "ana"), 91), which unpacks as for (i, name), score in .... Both are legal; the first reads better because the index stays at the front where a reader expects it.

Why wanting the index is usually a question worth checking

enumerate makes the index easy to get, which makes it worth asking, each time, whether you actually need it. A surprising share of loops that ask for a position are working around something else.

Comparing an item with its neighbour. The index is being used to reach items[i - 1] or items[i + 1]. What you want is consecutive pairs, and zip(items, items[1:]) or itertools.pairwise gives them without any index or any bounds check at the ends.

Walking two sequences together. The index is being used to index both. zip pairs them directly, and removes the possibility of the two lookups disagreeing.

Building a lookup of where things are. The loop collects positions into a dictionary. A dict comprehension over enumerate says it in one line, and often the real question is "does this contain x" or "which comes first", both of which have direct answers.

Modifying the list while walking it. The index is being used to assign back with items[i] = .... This works and is the one case where the index is genuinely required — but building a new list with a comprehension is usually clearer, and removing items by index while iterating is a bug in waiting.

What is left after those is the honest use: displaying a number to a person, reporting which record failed, or writing into a pre-sized structure. Those are real, and enumerate is exactly right for them.

Where you will actually meet it

Three situations account for most real uses, and they share a shape: the number is going somewhere a human will read it.

Reporting which line failed. Parsing a file and validating rows, the index is what turns "invalid date" into "line 47: invalid date". Without it the error message is useless on a file of any size, and start=1 matters because people count lines from one and so does every text editor.

Progress through a long job. if n % 1000 == 0: print(n) inside a loop over a large iterable gives you a heartbeat, and because enumerate is lazy it costs nothing on a stream you are already reading.

Numbered output. Menus, ranked results, numbered steps, table rows. Here the number is presentation, which is precisely the case start=1 exists for and precisely the case where using it to index would be wrong.

The common thread is that the index is an output rather than a mechanism. When the number is being printed, enumerate is the tool. When the number is being used to reach back into the data, there is usually a way to get the data directly instead.

Numbering things that are not lists

enumerate takes any iterable, and the interesting cases are the ones that are not sequences, because the position it hands you means something slightly different in each.

Over a file, the number is the line number, and it is the reason enumerate(f, start=1) appears in every script that reports problems in a data file. Nothing else gives you that number without reading the file twice.

Over a generator, the number counts what has been produced so far. It is the only way to know how far a stream has got, since a generator has no length and no position you can ask for.

Over a dictionary, the number is the position in insertion order, which is well defined since Python 3.7 and is occasionally what you want — numbering the entries of a config for display, say.

Over a set, the number is the position in an arbitrary order. It is stable within a single run and must not be relied on across runs or between machines, because it depends on hash values. If a number over a set matters, sort it first and enumerate the sorted result.

Over a string, the number is the character position, which makes enumerate(text) a reasonable way to find where something occurs — though str.find is usually the direct answer.

The pattern across all of them is that enumerate counts iterations, not positions in storage. For a list those are the same thing, and for everything else the distinction is the whole point.

The name, and where it came from

"Enumerate" means to list things one by one, and in older languages an enumerator was the object that walked a collection. Python's enumerate keeps the sense of walking while adding the numbering, which is why the name is about the traversal rather than about counting.

It arrived in Python 2.3, and the release note for it is unusually direct about the motivation: the range(len(...)) idiom was common, awkward, and a frequent source of small errors. The feature exists specifically to remove a pattern people kept writing, which is worth knowing because it tells you what the intended use is. If your loop looks like the pattern it replaced, use it. If it does not, the index it offers is probably not the thing you need.

Questions people ask

Does enumerate build a list? No. It yields pairs on demand, so it works on files, generators and anything else iterable.

Can I use it on a dictionary? Yes, and you get positions alongside keys. enumerate(d.items()) when you want the pairs too.

What if I only want the index? Then you probably want range(len(items)) — but check first, because wanting only the index is usually a sign the loop is doing something else.

How do I name the unused half? _ by convention, for either position: for _, item in enumerate(items) is legal though pointless.

Does start= accept a negative number? Yes. It is just the number to begin counting from, and nothing validates it.

Is enumerate slower than a manual counter? No, it is faster — the counting happens in C rather than in a bytecode += 1.

Can I enumerate backwards? Not directly. enumerate(reversed(items)) numbers from zero at the end; to get the original indices descending, zip a reversed range instead.

Can I unpack a nested item directly? Yes, with brackets: for i, (a, b) in enumerate(pairs). The pattern on the left has to match the shape of what is yielded.

Why is my counter wrong when I use continue? If it is a manual counter placed after the continue, it never runs for skipped items. enumerate has no such problem, because the counting is not in your loop body.

Can I start the count from something other than a number? No. start is added to an integer counter, so it has to be an integer.

Is there a version that gives the index from the end? Not built in. zip(range(len(a) - 1, -1, -1), a) does it, and is a good example of the arithmetic enumerate normally saves you.

Recap in one screen

  • enumerate yields (position, item); the for i, x on the left is ordinary tuple unpacking.
  • It removes both the manual counter and the range(len(...)) indexing, along with the mistakes each invites.
  • start= changes the label, not the position — never use that number to index back into the sequence.
  • It is lazy and works on anything iterable, including files too large to hold.
  • If the loop skips items and you want to number the output, use a counter; enumerate counts the input.

Check yourself

0 of 3

Answer without scrolling back up.

  1. What does `enumerate(['a', 'b'])` yield?

  2. With `enumerate(names, start=1)`, which item does n=1 refer to?

  3. Why prefer enumerate over `for i in range(len(items))`?

Cheat sheet

enumerate()

enumerate walks a sequence and hands you the position along with the item. It replaces both of the usual workarounds — a counter you increment by hand, and indexing back into the list.

PYTHON · vizlearn.in/python/enumerate_function.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.