← Home

087. Enumerate()

Access both index and value when iterating without manual counters

087. Enumerate()

He generates a ranked process report. The top process is #1, not #0. He uses a manual counter:

i = 0
for proc in sorted_processes:
    print(f"{i}. {proc['name']}: {proc['rss_mb']} MB")
    i += 1

A senior dev flags the PR: “Use enumerate.”

for rank, proc in enumerate(sorted_processes, start=1):
    print(f"{rank}. {proc['name']}: {proc['rss_mb']} MB")

Same result. No manual counter. No off-by-one bugs. He also uses enumerate when he needs to find which position in the list a process occupies — without keeping a separate index variable.

def find_rank(processes, name):
    for i, proc in enumerate(processes):
        if proc["name"] == name:
            return i  # 0-based rank
    return -1

💡 Fun fact: enumerate() is implemented in C inside CPython and is highly optimized — it produces (index, value) tuples lazily without building any intermediate list. The start parameter was added in Python 2.6 after widespread demand from developers who kept writing enumerate(items, 1) workarounds.

⚠️ Watch out: The most common enumerate mistake is forgetting to unpack the tuple — writing for item in enumerate(items) instead of for i, item in enumerate(items). This gives you tuples like (0, "Pizza") instead of a clean index and value, and downstream code silently operates on the wrong type.

🤔 Think about it: enumerate(items, start=1) gives 1-based numbering, but the underlying list is still 0-indexed. If you use the enumerated index to access items[i], you’ll get an off-by-one error. How would you safely use the index from enumerate(items, start=1) to look up elements?

Learning objectives

  • Use enumerate() to get index and value simultaneously
  • Set a custom starting index with the start parameter
  • Replace manual counter variables with enumerate

Key concepts

  • enumerate
  • index-value pairs
  • start parameter

Try it

Concept detail

enumerate(iterable) wraps an iterable and yields (index, value) tuples.

for i, item in enumerate(["a", "b", "c"]):
    print(i, item)
# 0 a
# 1 b
# 2 c

Start at a custom index: for i, item in enumerate([“a”, “b”, “c”], start=1): print(i, item) # 1 a # 2 b # 3 c

WHY enumerate instead of a manual counter: # Manual — error-prone, easy to forget to increment: i = 0 for item in items: print(i, item) i += 1 # easy to forget

# enumerate — no counter to manage:
for i, item in enumerate(items):
    print(i, item)

Materialize as list: list(enumerate([“a”, “b”])) # → [(0, ‘a’), (1, ‘b’)]

Combine with list comprehension: [f“{i}: {v}“ for i, v in enumerate(items, start=1)]

enumerate is lazy — it generates pairs one at a time, no list built in memory.

Solution

def numbered_menu(items):
    return [f"{i}. {item}" for i, item in enumerate(items, start=1)]

def find_index(items, target):
    for i, item in enumerate(items):
        if item == target:
            return i
    return -1

Tests

def test_numbered_menu_basic():
    result = numbered_menu(["Pizza", "Pasta", "Salad"])
    assert result[0] == "1. Pizza"
    assert result[1] == "2. Pasta"
    assert result[2] == "3. Salad"

def test_numbered_menu_starts_at_1():
    result = numbered_menu(["A"])
    assert result[0].startswith("1.")

def test_numbered_menu_single():
    assert numbered_menu(["Only"]) == ["1. Only"]

def test_numbered_menu_empty():
    assert numbered_menu([]) == []

def test_find_index_found():
    assert find_index(["a", "b", "c"], "b") == 1

def test_find_index_first():
    assert find_index(["x", "y", "z"], "x") == 0

def test_find_index_last():
    assert find_index(["a", "b", "c"], "c") == 2

def test_find_index_not_found():
    assert find_index(["a", "b"], "z") == -1

Resources