The enumerate(iterable, start=0) built-in function pairs each item in an iterable with an automatically incremented counter, returning an iterator of (index, item) tuples.
Idiomatic Index Tracking: In Python, manual indexing with range(len(seq)) is considered an anti-pattern. enumerate() eliminates manual counter tracking in for loops and works directly across lists, tuples, strings, and dictionary views in constant O(1) auxiliary space. To pair multiple sequences side-by-side instead of adding counter numbers, use zip(), or sort sequences with sorted().
The enumerate() function accepts an iterable container and an optional starting counter:
# Formal syntax of built-in enumerate()
enumerate_obj = enumerate(iterable, start=0)
| Parameter | Type Accepted | Behavioral Details |
|---|---|---|
iterable |
Iterable[T] |
Any object supporting iteration (list, tuple, string, generator, or dict view). |
start |
int (optional) |
Starting integer index for the counter. Defaults to 0 if omitted. |
| Return Value | enumerate object |
A lazy iterator yielding consecutive 2-tuples of format (count, element). |
When iterating through collections, unpacking index, item directly inside the loop header is clear, concise, and eliminates manual counter variables:
# 1. Basic list enumeration
my_list = ['One', 'Two', 'Three', 'Four']
en = enumerate(my_list)
print("Return type: ", type(en))
print("Pairs as list:", list(en))
# 2. Formatted iteration over list items
names = ['Alex', 'Ronald', 'John']
for idx, name in enumerate(names, start=1):
print(f"Name #{idx}: {name}")
Output:
Return type:
Pairs as list: [(0, 'One'), (1, 'Two'), (2, 'Three'), (3, 'Four')]
Name #1: Alex
Name #2: Ronald
Name #3: John
By passing the optional start parameter, you can begin numbering from any integer value (such as 1 for human-facing reports or 100 for batch IDs):
# 1. Setting custom starting index on a tuple
items = ('apple', 'banana', 'cherry')
for idx, fruit in enumerate(items, start=100):
print(f"Fruit {idx}: {fruit}")
# 2. String enumeration with default start (0)
text = "plus2net"
for idx, char in enumerate(text):
print(f"{idx}->{char}", end=" ")
print()
Output:
Fruit 100: apple
Fruit 101: banana
Fruit 102: cherry
0->p 1->l 2->u 3->s 4->2 5->n 6->e 7->t
Using range(len(seq)) is common among programmers coming from C or Java, but is clunky and inefficient in Python:
| Aspect | for i, item in enumerate(seq) |
for i in range(len(seq)) |
|---|---|---|
| Readability | Idiomatic Python; binds item directly | Cluttered; requires manual indexing seq[i] |
| Container Scope | Supports any iterable (streams, generators) | Requires sequence supporting len() and indexing |
| Safety | Zero risk of IndexError |
Prone to off-by-one errors and out-of-bound errors |
| Performance | Single iterator pass in compiled C | Extra method lookup overhead on every iteration |
languages = ['Python', 'C++', 'Java', 'Ruby']
# Anti-pattern: manual indexing with range(len(...))
# Requires separate lookup languages[i], error-prone and unpythonic
for i in range(len(languages)):
print(f"Index {i}: {languages[i]}")
# Idiomatic Python: enumerate() generates (index, item) directly in O(1) space
for i, lang in enumerate(languages):
print(f"Index {i}: {lang}")
Output:
Index 0: Python
Index 1: C++
Index 2: Java
Index 3: Ruby
Index 0: Python
Index 1: C++
Index 2: Java
Index 3: Ruby
When iterating over nested data structures like lists of tuples or dictionary items, wrap the nested components in parentheses to unpack both the counter and inner items simultaneously:
# 1. Unpacking list of tuples with index
students = [('Alice', 85), ('Bob', 90), ('Charlie', 78)]
for rank, (name, score) in enumerate(students, start=1):
print(f"Rank {rank}: {name} scored {score}")
# 2. Enumerating dictionary key-value items
config = {'host': 'localhost', 'port': 8080, 'debug': True}
for step, (key, val) in enumerate(config.items(), start=1):
print(f"Step {step}: {key} = {val}")
Output:
Rank 1: Alice scored 85
Rank 2: Bob scored 90
Rank 3: Charlie scored 78
Step 1: host = localhost
Step 2: port = 8080
Step 3: debug = True
Here are two production patterns where enumerate() provides clear, clean solutions:
# Recipe A: Finding all matching line numbers in text streams
def find_keyword_lines(lines: list[str], keyword: str):
matches = []
for line_no, content in enumerate(lines, start=1):
if keyword in content:
matches.append((line_no, content.strip()))
return matches
log_entries = [
"2026-10-14 08:00:00 INFO Service started",
"2026-10-14 08:01:10 WARN High latency",
"2026-10-14 08:02:15 ERROR Database disconnect",
"2026-10-14 08:03:00 ERROR Retry attempt failed",
]
errors = find_keyword_lines(log_entries, "ERROR")
for num, text in errors:
print(f"Line {num}: {text}")
# Recipe B: In-place array updates by tracking position
scores = [45, 52, 38, 60]
for idx, val in enumerate(scores):
if val < 50:
scores[idx] += 10 # Grace mark added directly
print("Curved scores:", scores)
Output:
Line 3: 2026-10-14 08:02:15 ERROR Database disconnect
Line 4: 2026-10-14 08:03:00 ERROR Retry attempt failed
Curved scores: [55, 52, 48, 60]
for i, k, v in enumerate(pairs) fails with ValueError because enumerate() always returns a 2-tuple. You must write for i, (k, v) in enumerate(pairs).enumerate object is a generator-like iterator. Once traversed, it cannot be iterated again without creating a fresh call.enumerate() loop; the function increments the counter automatically on each step.# Pitfall 1: Missing nested parenthesis when unpacking
pairs = [("a", 1), ("b", 2)]
# Incorrect: ValueError or unexpected unpacking
# for i, k, v in enumerate(pairs): # ValueError: not enough values to unpack!
# Correct: Group nested tuple elements inside parentheses
for i, (k, v) in enumerate(pairs):
print(f"Index {i}: {k} -> {v}")
# Pitfall 2: Iterator exhaustion
enum_it = enumerate(['x', 'y'])
print("First pass: ", list(enum_it))
print("Second pass:", list(enum_it)) # Empty iterator!
Output:
Index 0: a -> 1
Index 1: b -> 2
First pass: [(0, 'x'), (1, 'y')]
Second pass: []
Practice index tracking, nested tuple unpacking, and stream line scanning in our companion Jupyter Notebook:
Explore interactive code cells, benchmark enumerate vs range(len()), unpack complex nested collections, and run self-validating assertions.
View on GitHub Run in Google ColabRun these unit checks directly in Python or your local environment to verify enumerate() mechanics:
def test_enumerate_mechanics():
# 1. Default zero-indexed enumeration
sample = ['alpha', 'beta', 'gamma']
assert list(enumerate(sample)) == [(0, 'alpha'), (1, 'beta'), (2, 'gamma')]
# 2. Custom start offset
assert list(enumerate(sample, start=5)) == [(5, 'alpha'), (6, 'beta'), (7, 'gamma')]
# 3. String character enumeration
assert list(enumerate("hi")) == [(0, 'h'), (1, 'i')]
# 4. Single-pass iterator exhaustion
it = enumerate([10, 20])
assert list(it) == [(0, 10), (1, 20)]
assert list(it) == []
print("All enumerate() unit assertions passed! 100% verified.")
test_enumerate_mechanics()
Output:
All enumerate() unit assertions passed! 100% verified.
Combines elements from multiple iterables in parallel into unified tuples.
Builds a new sorted list from any iterable using stable Timsort ordering.
Returns a reverse iterator over sequences without duplicating underlying memory.
Extracts elements from an iterable for which a predicate function evaluates to True.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.