088. Zip()
Pair up multiple iterables and process them together
088. Zip()
He takes a RAM snapshot every 10 seconds. He wants to compare current RSS to the previous snapshot to detect spikes — processes that grew more than 50 MB.
He has two parallel lists: current and previous RSS values. He needs to pair them up.
current = [620, 85, 310, 12]
previous = [400, 82, 308, 11]
names = ["chrome", "python", "slack", "vim"]
# Pair up all three lists in one loop
for name, curr, prev in zip(names, current, previous):
delta = curr - prev
if delta > 50:
print(f"SPIKE: {name} grew {delta} MB")
# → SPIKE: chrome grew 220 MBIndex arithmetic (previous[i], current[i]) works but clutters the intent. zip makes the parallel structure explicit.
💡 Fun fact: Python’s zip() is named after the action of a zipper — it interleaves elements from multiple sequences just like a zipper’s teeth interlock. The function exists in nearly every functional programming language under names like zip, zipWith, or transpose. Python 3 made zip() lazy (an iterator), unlike Python 2 where it built a full list immediately.
⚠️ Watch out: zip() silently stops at the shortest iterable. If your names list has 5 elements but scores has only 4, the 5th name is silently dropped with no error. Use itertools.zip_longest() if you need to detect length mismatches — it fills missing values with None instead of stopping early.
🤔 Think about it: zip(*pairs) is the inverse of zip(list_a, list_b) — it “unzips” a list of pairs back into two separate tuples. Why does Python use the same * unpacking operator for both “spread a list into arguments” and “transpose a matrix”? Is this a clever reuse of syntax or a potential source of confusion?
Learning objectives
- Use zip() to iterate over two iterables in parallel
- Unpack zipped pairs in a for loop
- Use zip(*pairs) to unzip a list of tuples
Key concepts
- zip
- parallel iteration
- unpacking
- unzip
Try it
Concept detail
zip(a, b) combines two iterables into pairs, one from each at a time.
zip(["Alice", "Bob"], [95, 78])
# yields: ("Alice", 95), ("Bob", 78)
for name, score in zip(names, scores):
print(f"{name}: {score}")zip with 3 or more iterables: for a, b, c in zip(list1, list2, list3): …
zip stops at the shortest iterable: zip([1, 2, 3], [“a”, “b”]) # yields (1,“a”), (2,“b”) — stops at 2
Materialize as list of tuples: list(zip(names, scores)) # → [(“Alice”, 95), (“Bob”, 78)]
Unzip (transpose) a list of pairs: pairs = [(“Alice”, 95), (“Bob”, 78)] names, scores = zip(*pairs) # names = (“Alice”, “Bob”), scores = (95, 78)
WHY zip instead of range(len(…)): # Index arithmetic — clutters intent: for i in range(len(names)): print(names[i], scores[i])
# zip — reads like parallel iteration:
for name, score in zip(names, scores):
print(name, score)itertools.zip_longest fills missing values with a default instead of stopping early.
Solution
def make_report(names, scores):
return [f"{name}: {score}" for name, score in zip(names, scores)]
def dot_product(v1, v2):
return sum(a * b for a, b in zip(v1, v2))
def unzip_pairs(pairs):
names, scores = zip(*pairs)
return list(names), list(scores)Tests
def test_make_report():
result = make_report(["Alice", "Bob"], [95, 78])
assert result[0] == "Alice: 95"
assert result[1] == "Bob: 78"
def test_make_report_format():
result = make_report(["Carol"], [88])
# broken code gives "88: Carol" — name and score swapped
assert result[0].startswith("Carol")
def test_dot_product():
assert dot_product([1, 2, 3], [4, 5, 6]) == 32
def test_dot_product_unit():
# [1,0] · [0,1] = 0 (orthogonal vectors)
assert dot_product([1, 0], [0, 1]) == 0
def test_dot_product_identity():
assert dot_product([3, 4], [3, 4]) == 25 # 9 + 16
def test_unzip_pairs():
names, scores = unzip_pairs([("Alice", 95), ("Bob", 78)])
assert list(names) == ["Alice", "Bob"]
assert list(scores) == [95, 78]
def test_unzip_pairs_order():
names, scores = unzip_pairs([("Zara", 100), ("Adam", 60)])
assert names[0] == "Zara"
assert scores[0] == 100