What it is

Python’s enumerate() is a built-in that adds a running index to any iterable, yielding pairs of (index, value). It returns a lightweight “enumerate object” you can iterate over directly or cast to a list. See the official documentation for the full signature.

Why it matters

  • Removes error-prone manual counters in loops.
  • Stays readable and “Pythonic” compared to range(len(...)).
  • Works lazily, so it’s efficient for large iterables.

How it works

By default, the index starts at 0. You can change that with the optional start argument.

# Basic usage
runners = ["Lenka", "Martina", "Gugu"]
for i, name in enumerate(runners):
    print(i, name)
# 0 Lenka
# 1 Martina
# 2 Gugu

# One-based indexing
for position, name in enumerate(runners, start=1):
    print(position, name)
# 1 Lenka
# 2 Martina
# 3 Gugu

Under the hood, enumerate() yields a pair on each iteration. You can materialize it to inspect the structure:

seasons = ["Spring", "Summer", "Fall", "Winter"]
print(list(enumerate(seasons)))
# [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]

It’s also a regular iterator, so you can step through it with next():

it = enumerate(["a", "b", "c"], start=10)
print(next(it))  # (10, 'a')
print(next(it))  # (11, 'b')
print(next(it))  # (12, 'c')

How to use it (practical patterns)

1) Highlight the first item or a specific step

tasks = ["Pay Rent", "Clean Dishes", "Buy Milk"]
for idx, task in enumerate(tasks):
    if idx == 0:
        print(f"* {task.upper()}!")
    else:
        print(f"* {task}")
# * PAY RENT!
# * Clean Dishes
# * Buy Milk

2) Grab every second character from a string

secret = "3LAigf7eq 5fhiOnpdDs2 Ra6 nwUalyo.9"
message = []
for i, ch in enumerate(secret):
    if i % 2:           # odd indexes only
        message.append(ch)
print("".join(message))

3) Add line numbers while scanning file content

lines = [
    "This is a\tline",
    "This line is fine",
    "Another line with whitespace "
]
for lineno, line in enumerate(lines, start=1):
    if "\t" in line:
        print(f"Line {lineno}: Contains a tab character.")
    if line.rstrip() != line:
        print(f"Line {lineno}: Contains trailing whitespace.")

4) Iterate dictionaries with an index

In modern Python, dicts preserve insertion order, so you can index the pairs consistently:

months = {
    "january": "winter",
    "february": "winter",
    "march": "spring",
}
for idx, (month, season) in enumerate(months.items(), start=1):
    print(f"{idx}: {month} - {season}")
# 1: january - winter
# 2: february - winter
# 3: march - spring

Limits and trade‑offs

  • It’s an iterator, not a list. You can loop it once. If you need random access, cast to list(...) first.
  • It indexes a single iterable. If you’re pairing multiple sequences, zip(...) is a better fit.
  • Avoid range(len(seq)) unless you truly need indexes only. enumerate() communicates both intent and structure more clearly.

When not to use enumerate()

  • Multi-sequence iteration: Prefer zip(a, b) and unpack both values directly.
  • Slicing tasks: If you only need a slice like “first 4 chars,” use slicing, not enumerate() plus conditionals.
  • Adjacent pairs: For routes or consecutive items, reach for tools like itertools.pairwise rather than manual indexing.
# zip() beats enumerate()+index reuse for multi-sequence loops
pets   = ["Leo", "Aubrey", "Frieda"]
owners = ["Bartosz", "Sarah Jane", "Philipp"]
for pet, owner in zip(pets, owners):
    print(f"{pet} & {owner}")

FAQs

What does enumerate() return?
An enumerate object that yields (index, value) tuples as you iterate.

How do I start counting from 1?
Pass start=1 to enumerate(iterable, start=1).

Can I reuse the same enumerate object?
Not after it’s been consumed. It’s an iterator. Create a new one or materialize it as a list if you need multiple passes.

Is enumerate() faster than manual counters?
It’s implemented efficiently and removes bookkeeping from your code. Beyond performance, its clarity is the main win.