Python Basics — range(), enumerate(), zip()

3 minute read

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 iterable
  • zip(iter1, iter2, ...) pairs elements from multiple iterables
  • Use enumerate() instead of range(len(...))
  • zip() stops at the shortest iterable; use zip_longest() to include all
  • All three return lazy objects — they generate values on demand

Next up: Nested Loops →

Updated: