The zip(*iterables, strict=False) built-in function pairs corresponding elements from multiple iterables into tuples and yields them as a lazy iterator.
Parallel Stream Traversal: zip() consumes items in lockstep in O(1) auxiliary space without generating intermediate lists. By default, it stops as soon as the shortest iterable is exhausted. In Python 3.10+, passing strict=True prevents silent data loss by enforcing equal container lengths. To unpack paired data back into separate sequences, use the zip(*zipped) idiom, or add sequential counter indexes with enumerate().
The zip() function takes zero or more iterables and returns an iterator of tuples:
# Formal syntax of built-in zip()
# Python 3.10+ supports optional strict=True parameter
zip_iterator = zip(*iterables, strict=False)
| Parameter | Type Accepted | Behavioral Details |
|---|---|---|
*iterables |
Zero or more Iterable[T] |
Any iterable collections (list, tuple, string, set, dict, generator). |
strict |
bool (optional, Python 3.10+) |
When True, raises ValueError if any iterable terminates earlier than others. Defaults to False. |
| Return Value | zip object |
A lazy iterator yielding consecutive n-tuples containing items from each input iterable. |
When you have related data spread across multiple lists or sequences, zip() unites them into matching tuples on each iteration:
# 1. Zipping multiple lists into tuples
names = ['Ravi', 'Raju', 'Alex']
ids = [1, 2, 3]
math_scores = [30, 40, 50]
zipped_data = zip(names, ids, math_scores)
print("Type of zip object:", type(zipped_data))
print("Zipped list:", list(zipped_data))
# 2. Materializing to set and tuple
re_zip = zip(names, ids, math_scores)
print("Zipped tuple:", tuple(re_zip))
Output:
Type of zip object:
Zipped list: [('Ravi', 1, 30), ('Raju', 2, 40), ('Alex', 3, 50)]
Zipped tuple: (('Ravi', 1, 30), ('Raju', 2, 40), ('Alex', 3, 50))
By default, zip() halts as soon as the shortest iterable finishes. Any excess elements in longer iterables are discarded silently:
# Unequal lengths: default zip terminates at shortest iterable
names = ['Ravi', 'Raju', 'Alex']
ids = [1, 2] # Only 2 elements
math_scores = [30, 40, 50]
truncated = zip(names, ids, math_scores)
print("Shortest length termination:", list(truncated))
Output:
Shortest length termination: [('Ravi', 1, 30), ('Raju', 2, 40)]
You can reverse the grouping process using the * unpacking operator. Passing *zipped_data unpacks the tuples as individual arguments back into zip(), separating them into original series:
# Unzipping using the * sequence unpacking operator
zipped_pairs = [('Ravi', 1, 30), ('Raju', 2, 40), ('Alex', 3, 50)]
names_out, ids_out, scores_out = zip(*zipped_pairs)
print("Unzipped names: ", names_out)
print("Unzipped IDs: ", ids_out)
print("Unzipped scores:", scores_out)
Output:
Unzipped names: ('Ravi', 'Raju', 'Alex')
Unzipped IDs: (1, 2, 3)
Unzipped scores: (30, 40, 50)
A classic Python idiom is pairing a list of keys with a list of values to create a dictionary in a single readable line:
# Fast dictionary construction from parallel lists
keys = ["name", "age", "city"]
values = ["Alice", 25, "New York"]
person = dict(zip(keys, values))
print("Constructed dictionary:", person)
# Two-column tabular record pairing
fields = ["sku", "stock", "price"]
row = ["A101", 45, 19.99]
item_record = dict(zip(fields, row))
print("Item record: ", item_record)
Output:
Constructed dictionary: {'name': 'Alice', 'age': 25, 'city': 'New York'}
Item record: {'sku': 'A101', 'stock': 45, 'price': 19.99}
When inputs must be the same length, Python provides two robust options:
itertools.zip_longest(): Fills missing slots with a placeholder like None or "N/A".zip(..., strict=True) (Python 3.10+): Immediately raises a ValueError if any iterable length differs.from itertools import zip_longest
names = ["Alice", "Bob"]
ages = [25, 30, 35]
# Option A: zip_longest fills missing values with fillvalue
padded = list(zip_longest(names, ages, fillvalue="N/A"))
print("zip_longest result:", padded)
# Option B: Python 3.10+ strict=True raises ValueError on length mismatch
try:
list(zip(names, ages, strict=True))
except ValueError as err:
print("strict=True caught mismatch:", err)
Output:
zip_longest result: [('Alice', 25), ('Bob', 30), ('N/A', 35)]
strict=True caught mismatch: zip() argument 2 is longer than argument 1
Below are two common real-world algorithms powered cleanly by zip():
# Recipe A: Matrix transposition with zip(*matrix)
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
transposed = [list(col) for col in zip(*matrix)]
print("Transposed matrix:")
for row in transposed:
print(" ", row)
# Recipe B: Consecutive element pair iteration (sliding window of 2)
sequence = [10, 15, 22, 28, 35]
deltas = [b - a for a, b in zip(sequence, sequence[1:])]
print("Consecutive differences:", deltas)
Output:
Transposed matrix:
[1, 4, 7]
[2, 5, 8]
[3, 6, 9]
Consecutive differences: [5, 7, 6, 7]
zip() drops trailing elements can mask bugs. Always use strict=True when inputs are expected to have identical lengths.zip object empties after one iteration pass. Materialize with list(zip(...)) if multiple passes are needed.zip(*[]) raises ValueError: not enough values to unpack if unpacked into multiple target variables.# Pitfall 1: Silent data loss with unequal lengths without strict=True
user_ids = [101, 102, 103, 104]
emails = ["a@test.com", "b@test.com"] # Missing 2 emails!
# Silent truncation: 103 and 104 are dropped without warning!
pairs = list(zip(user_ids, emails))
print("Silent truncation dropped items:", pairs)
# Pitfall 2: Iterator exhaustion
z = zip([1, 2], ['a', 'b'])
print("First pass: ", list(z))
print("Second pass:", list(z)) # Empty!
Output:
Silent truncation dropped items: [(101, 'a@test.com'), (102, 'b@test.com')]
First pass: [(1, 'a'), (2, 'b')]
Second pass: []
Practice parallel sequence pairing, matrix transpositions, and unzipping drills in our companion Jupyter Notebook:
Explore interactive code cells, benchmark zip performance, test strict=True validation, and run self-validating assertions.
View on GitHub Run in Google ColabRun these unit checks directly in Python or your local environment to verify zip() mechanics:
def test_zip_mechanics():
# 1. Parallel pairing
assert list(zip([1, 2], ['a', 'b'])) == [(1, 'a'), (2, 'b')]
# 2. Unzipping with * operator
unzipped = list(zip(*[(1, 'a'), (2, 'b')]))
assert unzipped == [(1, 2), ('a', 'b')]
# 3. Shortest sequence termination
assert list(zip([1, 2, 3], ['x', 'y'])) == [(1, 'x'), (2, 'y')]
# 4. Dictionary construction
d = dict(zip(['k1', 'k2'], [10, 20]))
assert d == {'k1': 10, 'k2': 20}
# 5. strict=True verification in Python 3.10+
try:
list(zip([1, 2], [10], strict=True))
assert False, "Should raise ValueError"
except ValueError:
pass
print("All zip() unit assertions passed! 100% verified.")
test_zip_mechanics()
Output:
All zip() unit assertions passed! 100% verified.
Pairs an iterable's items with an automatically incremented integer counter.
Applies a function across elements of one or more iterables in parallel.
Extracts elements from an iterable matching a boolean predicate condition.
Returns a new sorted list from any iterable collection using Timsort.
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.