Python Basics — range(), enumerate(), zip()
range(), enumerate(), zip()
These three built-in functions are essential tools for writing clean, Pythonic loops.
range() Deep Dive
range() generates a sequence of integers. It doesn’t create a list — it’s a lazy object that produces numbers on demand.
Three Forms
# range(stop) — 0 to stop-1
list(range(5)) # [0, 1, 2, 3, 4]
# range(start, stop) — start to stop-1
list(range(2, 7)) # [2, 3, 4, 5, 6]
# range(start, stop, step)
list(range(0, 10, 2)) # [0, 2, 4, 6, 8]
Negative Steps (Counting Down)
list(range(5, 0, -1)) # [5, 4, 3, 2, 1]
list(range(10, 0, -2)) # [10, 8, 6, 4, 2]
list(range(0, -5, -1)) # [0, -1, -2, -3, -4]
Memory Efficiency
range() uses constant memory regardless of size:
import sys
# A list of 1 million numbers
big_list = list(range(1_000_000))
print(sys.getsizeof(big_list)) # ~8,000,056 bytes
# A range of 1 million numbers
big_range = range(1_000_000)
print(sys.getsizeof(big_range)) # 48 bytes!
range() Operations
r = range(0, 20, 3)
print(5 in r) # False
print(6 in r) # True
print(len(r)) # 7
print(r[2]) # 6
print(r[-1]) # 18
print(r[1:4]) # range(3, 12, 3)
enumerate() — Index + Value
enumerate() wraps an iterable and returns (index, value) pairs:
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
Output:
0: apple
1: banana
2: cherry
Custom Start Index
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")
Output:
1. apple
2. banana
3. cherry
Why Not Use range(len(…))?
# Not Pythonic
for i in range(len(fruits)):
print(f"{i}: {fruits[i]}")
# Pythonic
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
Both work, but enumerate() is cleaner and less error-prone.
Practical Use
lines = ["First line", "Second line", "Third line"]
for line_num, line in enumerate(lines, start=1):
print(f"Line {line_num}: {line}")
Output:
Line 1: First line
Line 2: Second line
Line 3: Third line
zip() — Parallel Iteration
zip() pairs elements from two or more iterables:
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
for name, age in zip(names, ages):
print(f"{name} is {age}")
Output:
Alice is 25
Bob is 30
Charlie is 35
Multiple Iterables
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
cities = ["NYC", "London", "Tokyo"]
for name, age, city in zip(names, ages, cities):
print(f"{name}, {age}, {city}")
Output:
Alice, 25, NYC
Bob, 30, London
Charlie, 35, Tokyo
Unequal Lengths — zip Stops at Shortest
a = [1, 2, 3, 4, 5]
b = ["x", "y", "z"]
print(list(zip(a, b))) # [(1, 'x'), (2, 'y'), (3, 'z')]
zip_longest from itertools
To include all elements, even when lengths differ:
from itertools import zip_longest
a = [1, 2, 3, 4, 5]
b = ["x", "y", "z"]
print(list(zip_longest(a, b, fillvalue="-")))
# [(1, 'x'), (2, 'y'), (3, 'z'), (4, '-'), (5, '-')]
Unzipping
pairs = [("Alice", 95), ("Bob", 87), ("Charlie", 92)]
names, scores = zip(*pairs)
print(names) # ('Alice', 'Bob', 'Charlie')
print(scores) # (95, 87, 92)
Building a Dictionary with zip
keys = ["name", "age", "city"]
values = ["Alice", 25, "NYC"]
person = dict(zip(keys, values))
print(person) # {'name': 'Alice', 'age': 25, 'city': 'NYC'}
Comparison Table
| Function | Purpose | Returns | Common With |
|---|---|---|---|
range() |
Generate number sequence | range object | for i in range(n) |
enumerate() |
Add index to iterable | enumerate object | for i, val in enumerate(list) |
zip() |
Pair multiple iterables | zip object | for a, b in zip(x, y) |
Combining Them
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
for rank, (name, score) in enumerate(zip(names, scores), start=1):
print(f"#{rank}: {name} — {score}")
Output:
#1: Alice — 95
#2: Bob — 87
#3: Charlie — 92
Summary
range(start, stop, step)generates number sequences lazily (memory efficient)enumerate(iterable, start)adds an index counter to any iterablezip(iter1, iter2, ...)pairs elements from multiple iterables- Use
enumerate()instead ofrange(len(...)) zip()stops at the shortest iterable; usezip_longest()to include all- All three return lazy objects — they generate values on demand
Next up: Nested Loops →