enumerate and zip are essential Python tools. Here's how to use them.

enumerate

Get index and value together:

# Without enumerate
fruits = ["apple", "banana", "cherry"]
for i in range(len(fruits)):
    print(f"{i}: {fruits[i]}")
 
# With enumerate
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")

Start from different index

for i, fruit in enumerate(fruits, start=1):
    print(f"{i}: {fruit}")
# 1: apple
# 2: banana
# 3: cherry

Common patterns

# Find index of item
for i, item in enumerate(items):
    if item == target:
        print(f"Found at index {i}")
        break
 
# Build dict with indices
index_map = {item: i for i, item in enumerate(items)}
 
# Numbered list
numbered = [f"{i}. {item}" for i, item in enumerate(items, 1)]

zip

Iterate over multiple sequences together:

names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
 
# Without zip
for i in range(len(names)):
    print(f"{names[i]}: {scores[i]}")
 
# With zip
for name, score in zip(names, scores):
    print(f"{name}: {score}")

Multiple sequences

names = ["Alice", "Bob"]
ages = [25, 30]
cities = ["NYC", "LA"]
 
for name, age, city in zip(names, ages, cities):
    print(f"{name}, {age}, from {city}")

Unequal lengths

a = [1, 2, 3]
b = [4, 5]
 
list(zip(a, b))  # [(1, 4), (2, 5)] - stops at shortest
 
# Use zip_longest for all elements
from itertools import zip_longest
list(zip_longest(a, b, fillvalue=0))
# [(1, 4), (2, 5), (3, 0)]

Strict mode (Python 3.10+)

# Raise error if lengths differ
list(zip(a, b, strict=True))
# ValueError: zip() argument 2 is shorter than argument 1

Unzipping

pairs = [("a", 1), ("b", 2), ("c", 3)]
 
# Unzip with *
letters, numbers = zip(*pairs)
# letters = ('a', 'b', 'c')
# numbers = (1, 2, 3)

Common patterns

# Create dict from two lists
names = ["a", "b", "c"]
values = [1, 2, 3]
d = dict(zip(names, values))
# {'a': 1, 'b': 2, 'c': 3}
 
# Transpose matrix
matrix = [[1, 2], [3, 4], [5, 6]]
transposed = list(zip(*matrix))
# [(1, 3, 5), (2, 4, 6)]
 
# Parallel assignment
for old, new in zip(old_names, new_names):
    rename(old, new)

Combining enumerate and zip

names = ["Alice", "Bob"]
scores = [85, 92]
 
for i, (name, score) in enumerate(zip(names, scores)):
    print(f"{i}: {name} scored {score}")

Performance

Both are lazy iterators—they don't create lists in memory:

# Memory efficient
for i, item in enumerate(huge_list):
    process(item)
 
# If you need a list
indexed = list(enumerate(items))
pairs = list(zip(a, b))

My Rules

  1. Use enumerate over range(len()) — always
  2. Use zip over manual indexing — cleaner code
  3. Check lengths — zip silently truncates
  4. Remember they're lazy — convert to list if needed
  5. Use strict=True — when lengths must match

These two functions appear in almost every Python file I write.

React to this post: