Python enumerate(): Get Index and Value in a For Loop (With Examples)
The Python enumerate() function lets you loop over an iterable while tracking both the index and the value at the same time. Instead of manually managing a counter variable, you wrap your list, string, or other iterable in enumerate() and unpack each item into two names: the position and the element. It returns a lazy iterator of (index, value) pairs, so it stays memory-efficient even on huge sequences.
In short: when you need the index and value together inside a for loop, enumerate() is the idiomatic, readable choice. It replaces the clunky range(len()) pattern, supports a custom start parameter, and works on any iterable, lists, tuples, strings, dictionary views, and generators alike. Below you’ll find how it works under the hood, runnable examples for each common case, and the gotchas that trip up newcomers.
Key Takeaways
•enumerate(iterable)yields(index, value)pairs, so you get the position and the element in one clean loop.
• The optionalstartparameter changes the first index:enumerate(items, start=1)begins counting at 1.
• It returns a lazy iterator, not a list, which keeps memory use flat even on millions of items.
• Preferenumerate()overrange(len(x)), it’s more readable and avoids repeated indexing.
• It works on any iterable: lists, strings, tuples, and dictionary views all enumerate cleanly.
Most tutorials present enumerate() as a convenience for getting an index, but the deeper value is what it removes: it eliminates an entire class of off-by-one and index-desync bugs. When you maintain a manual counter with i = 0 and i += 1, that counter is a separate piece of state that can drift out of sync with the loop, especially when you add continue statements or edit the loop body months later. enumerate() binds the index to the iteration itself, so the count can never lag behind the value. That tight coupling is the real reason experienced Python developers reach for it reflexively, readability is the visible benefit, but correctness is the structural one.
What does enumerate() do in Python?
enumerate() takes an iterable and returns an enumerate object, a lazy iterator that produces tuples of the form (count, element). On each iteration it pairs the next item from your iterable with an automatically incremented counter. Because it’s lazy, it computes pairs only as you request them, which is why it handles large or infinite iterables without loading everything into memory.
The function signature is simple: enumerate(iterable, start=0). The first argument is anything you can loop over. The second, start, sets the value of the first index and defaults to zero. You almost always consume the result directly in a for loop by unpacking each tuple into two variables.
fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
print(index, fruit)
# Output:
# 0 apple
# 1 banana
# 2 cherry
Here index receives the auto-incremented counter and fruit receives the value. The unpacking happens because each item enumerate() yields is a two-element tuple. You can also call list(enumerate(fruits)) to see the raw pairs: [(0, 'apple'), (1, 'banana'), (2, 'cherry')].
*Citation capsule:* The official Python documentation defines enumerate(iterable, start=0) as returning an enumerate object whose __next__() method returns a tuple containing a count (from start) and the corresponding value from the iterable (Python docs).
How do you get both index and value in a for loop?
Getting the index and value together is the single most common reason to use enumerate(). You unpack the yielded tuple into two loop variables, conventionally named index (or i) and a descriptive name for the element. This pattern reads almost like English and keeps both pieces of data in scope for the body of the loop.
colors = ["red", "green", "blue"]
for i, color in enumerate(colors):
print(f"Color #{i} is {color}")
# Output:
# Color #0 is red
# Color #1 is green
# Color #2 is blue
[IMAGE: a clean code editor showing a Python for loop with enumerate highlighted – search “python code editor screen”]
You’re free to use whatever names make sense. For coordinate data you might write for row_number, row in enumerate(grid). The mechanics never change: the left name binds to the counter, the right name binds to the element. If you ever need only the index, you can ignore the value with an underscore: for i, _ in enumerate(items), though at that point a plain range(len(items)) may communicate intent better.
*Citation capsule:* PEP 279 introduced the built-in enumerate() to provide a clean way to loop over a sequence while keeping a running index, addressing a pattern the proposal noted was “common enough to deserve a built-in” (PEP 279, Python.org).
How does the start parameter work?
The start parameter changes where the index begins counting. By default enumerate() starts at zero, matching Python’s zero-based indexing. But when you’re printing human-facing lists, numbering steps, or generating line numbers, starting at one is far more natural. Pass start=1 (or any integer) as the second argument.
steps = ["Mix ingredients", "Bake at 180C", "Let cool"]
for number, step in enumerate(steps, start=1):
print(f"Step {number}: {step}")
# Output:
# Step 1: Mix ingredients
# Step 2: Bake at 180C
# Step 3: Let cool
A common misconception is that start slices or skips items, it doesn’t. start only sets the value of the counter. The iterable is still walked from its very first element. So enumerate(steps, start=1) still includes “Mix ingredients”, it just labels it as 1 instead of 0. The start argument can even be negative if you have a reason to count from below zero.
[CHART: bar comparison of lines of code and readability – enumerate vs range(len) vs manual counter – source: author analysis]
enumerate() vs range(len()): which should you use?
You should almost always prefer enumerate() over range(len()) when you need both the index and the value. The range(len()) pattern works, but it indexes back into the list on every iteration (items[i]), which is more verbose, slightly slower, and easier to get wrong. enumerate() hands you the value directly, so there’s nothing to look up.
Here’s the same task written both ways so you can compare.
items = ["a", "b", "c"]
# The clunky way: range(len())
for i in range(len(items)):
print(i, items[i])
# The Pythonic way: enumerate()
for i, item in enumerate(items):
print(i, item)
Both print identical output, but the second version is shorter and removes the repeated items[i] indexing. The table below summarizes when each approach fits.
| Aspect | enumerate(items) |
range(len(items)) |
|---|---|---|
| Gets the value directly | Yes, via unpacking | No, you index items[i] |
| Readability | High, intent is obvious | Lower, extra indexing noise |
| Works on lazy iterables | Yes (generators, files) | No, needs len() |
| Best when you need the value | Strongly preferred | Avoid |
| Best when you need only indices | Use _ or skip it |
Reasonable choice |
The one case where range(len()) makes sense is when you genuinely don’t need the value, for example, generating numeric indices to build a new structure. Otherwise, enumerate() wins on clarity and works on iterables that have no length, like file objects and generators.
*Citation capsule:* The Real Python guide on enumerate() recommends it over manual counters and range(len()), noting that it produces “cleaner and more readable” loops while avoiding the need to manage a separate index variable (Real Python).
How do you enumerate a string, tuple, or dictionary?
enumerate() works on any iterable, not just lists. Strings yield their characters, tuples yield their elements, and dictionaries yield their keys by default. The behavior is consistent: you always get (index, value) pairs, where the value is whatever the iterable produces when looped over normally.
Enumerating a string gives you each character with its position:
word = "code"
for i, char in enumerate(word):
print(i, char)
# Output:
# 0 c
# 1 o
# 2 d
# 3 e
Dictionaries need a small note. Looping over a dict directly yields keys, so enumerate(my_dict) pairs an index with each key. If you want the index alongside both key and value, enumerate my_dict.items() and unpack the inner tuple:
prices = {"apple": 1.20, "banana": 0.50, "cherry": 3.00}
for i, (name, price) in enumerate(prices.items(), start=1):
print(f"{i}. {name}: ${price:.2f}")
# Output:
# 1. apple: $1.20
# 2. banana: $0.50
# 3. cherry: $3.00
Notice the nested unpacking: (name, price) destructures the (key, value) tuple that .items() yields. Since Python 3.7, dictionaries preserve insertion order, so the enumerated indices follow the order you defined the dictionary (Python docs).
What are common enumerate() mistakes to avoid?
The most frequent mistake is forgetting to unpack the tuple, which leaves you looping over (index, value) pairs as single objects. If you write for item in enumerate(items), then item becomes a tuple like (0, 'apple'), not the element you expected. Always unpack into two names unless you specifically want the pairs.
A second pitfall is assuming enumerate() returns a list. It returns a lazy iterator, so it’s single-use. Once you’ve looped through it, it’s exhausted, looping again yields nothing. If you need to reuse the pairs, wrap it in list() first:
pairs = list(enumerate(["x", "y", "z"]))
print(pairs) # [(0, 'x'), (1, 'y'), (2, 'z')]
print(pairs[1]) # (1, 'y') - now you can index and reuse it
A third mistake is using enumerate() purely for its index while ignoring the value, when a different construct would be clearer. If you only need a count of iterations and not positions into a sequence, itertools.count() or a simple range may express intent more directly. Match the tool to what you actually need.
How DarazHost supports your Python development
Mastering enumerate() is one small piece of writing real Python applications, and those applications need somewhere reliable to run. DarazHost developer hosting gives you full SSH access, your choice of Python version, and the freedom to install packages with pip, run virtual environments, and configure WSGI apps the way you want. You control the environment instead of fighting a locked-down shared box, which matters the moment your project grows past a single script.
For the bigger picture on setting up a proper Python workflow, see our pillar guide: Hosting for Developers: The Complete Guide to a Real Environment You Control.
Frequently asked questions
What does enumerate() return in Python?
enumerate() returns an enumerate object, which is a lazy iterator producing (index, value) tuples. It does not return a list. You typically consume it directly in a for loop, but you can convert it with list(enumerate(x)) if you need a reusable sequence of pairs you can index into later.
Can you start enumerate() at 1 instead of 0?
Yes. Pass the start parameter: enumerate(items, start=1). This sets the first index to 1, which is ideal for numbered, human-facing output like step lists or line numbers. The start value only changes the counter, it does not skip or slice any items from your iterable.
Is enumerate() faster than range(len())?
In typical code, enumerate() is as fast or slightly faster because it avoids repeated indexing into the sequence with items[i]. More importantly, it’s more readable and works on iterables without a length, like generators and file objects. Performance differences are usually negligible, choose enumerate() for clarity.
Can you enumerate a dictionary in Python?
Yes. enumerate(my_dict) pairs an index with each key, since looping a dict yields keys. To get the index plus both key and value, enumerate my_dict.items() and unpack: for i, (key, value) in enumerate(my_dict.items()). Since Python 3.7, dictionaries keep insertion order, so indices follow that order.
What’s the difference between enumerate() and zip()?
enumerate() adds an automatic index to one iterable, yielding (index, value) pairs. zip() pairs elements from two or more iterables together, yielding (a, b, ...) tuples without any counter. Use enumerate() when you need positions, and zip() when you need to iterate several sequences in parallel.