The Python zip() function combines items from two or more iterables position by position: first with first, second with second, and so on. Each step produces a tuple. The return value is a zip object—an iterator—not a list, so you usually wrap it with list() or loop over it with for. By default, zip() stops when the shortest iterable runs out, which is easy to forget when lengths do not match.
This guide covers the zip() function in Python with practical examples: quick reference, syntax, converting results, looping, unequal lengths, strict=True, zip_longest(), dictionaries, unzipping, and common mistakes. For walking two lists in a loop with more recipes, see loop through two lists in Python. Built-in zip() is not the zipfile module for archive files.
Tested on: Python 3.13.3; kernel 6.14.0-37-generic.
Python zip() quick reference
| Task | Use |
|---|---|
| Pair two lists item by item | zip(list1, list2) |
| Pair three or more iterables | zip(a, b, c) |
| See the output as a list | list(zip(...)) |
| Loop through two lists together | for x, y in zip(a, b): |
| Create a dictionary from keys and values | dict(zip(keys, values)) |
| Split zipped pairs back into separate values | zip(*pairs) |
| Stop silently at the shortest iterable | zip(a, b) |
| Raise an error if lengths differ | zip(a, b, strict=True) |
| Keep all values from the longest iterable | itertools.zip_longest() |
Use normal zip() when unmatched values can be ignored. Use strict=True when different lengths indicate a bug. Use zip_longest() when missing values must be filled instead of dropped.
What does zip() do in Python?
zip() walks multiple iterables in lockstep. The first item from each input forms the first tuple, the second items form the second tuple, and so on. It works with lists, tuples, strings, ranges, and other iterables. In Python 3, the result is a lazy iterator (a zip object), not a materialized list.
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 77]
paired = zip(names, scores)
print(list(paired))You should see [('Alice', 85), ('Bob', 92), ('Charlie', 77)]. Each name is paired with the score at the same index—no manual range(len(...)) required.
With a single iterable, each tuple holds one element: list(zip([1, 2, 3])) → [(1,), (2,), (3,)].
Python zip() syntax
zip(*iterables, strict=False)*iterables— pass one, two, three, or more iterables. The star means “any number of positional iterables” (same idea as*argsin function definitions, but here it is part ofzip’s call signature).strict=False— default. Pair until the shortest iterable ends.strict=True— Python 3.10+. RaiseValueErrorif any iterable is longer or shorter than the others.
See the built-in zip() docs for the full signature.
Convert a zip object to a list, tuple, or dictionary
Printing zip(...) alone shows something like <zip object at 0x...>. Convert when you need to inspect or reuse the full result:
names = ["Alice", "Bob"]
scores = [85, 92]
print(list(zip(names, scores)))
print(tuple(zip(names, scores)))
keys = ["name", "age", "city"]
values = ["Alice", 30, "London"]
print(dict(zip(keys, values)))You should see [('Alice', 85), ('Bob', 92)], the same pairs as a tuple, then {'name': 'Alice', 'age': 30, 'city': 'London'}.
A zip object is consumed after one full iteration. If you need the pairs twice, store pairs = list(zip(a, b)) first:
z = zip(names, scores)
print(list(z))
print(list(z))You should see the pairs once, then an empty list [] on the second pass.
Loop through two or more lists with zip()
This is one of the most practical uses of the Python zip() function. It avoids index arithmetic and reads clearly when two lists are related by position. The same pattern works inside a for loop over any parallel iterables.
Two lists:
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 77]
for name, score in zip(names, scores):
print(f"{name}: {score}")Three lists:
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 77]
grades = ["B", "A", "A"]
for name, score, grade in zip(names, scores, grades):
print(name, score, grade)You should see three aligned rows. More patterns (indexes with enumerate, unequal lengths, building dicts) live in loop through two lists in Python.
What happens when zip() gets lists of different lengths?
By default, zip() stops when the shortest iterable is exhausted. Extra values in longer iterables are ignored—not an error.
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92]
print(list(zip(names, scores)))You should see [('Alice', 85), ('Bob', 92)]. Charlie has no score in the output.
That truncation is fine when you intentionally take the minimum length. It is dangerous when you expected every row to align—silent data loss.
zip(). Use strict=True to catch the mismatch, or zip_longest() when you need to keep every value.
Use strict=True when different lengths should be an error
strict=True (Python 3.10+) raises ValueError when iterables differ in length. Use it when lists must match exactly: IDs and names, CSV columns, test fixtures, parallel record fields.
ids = [1, 2, 3]
names = ["Alice", "Bob"]
try:
list(zip(ids, names, strict=True))
except ValueError as e:
print(e)You should see a message that one argument is shorter than the other. That fails fast instead of dropping 3.
When lengths are guaranteed equal, strict=True documents intent and catches bugs early. It does not pad or keep extras—use zip_longest() for that.
Use itertools.zip_longest() to keep unmatched values
itertools.zip_longest() continues until the longest iterable ends. Shorter inputs are padded with fillvalue (default None):
from itertools import zip_longest
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92]
print(list(zip_longest(names, scores, fillvalue="N/A")))You should see [('Alice', 85), ('Bob', 92), ('Charlie', 'N/A')]. The third name stays; the missing score is filled.
| Situation | Best choice |
|---|---|
| Ignore extras from longer iterables | zip() |
| Treat unequal lengths as a bug | zip(..., strict=True) |
| Keep all values and fill missing items | zip_longest() |
Import with from itertools import zip_longest or import itertools and call itertools.zip_longest(...).
Create a dictionary with zip()
dict(zip(keys, values)) is a common Python pattern: the first iterable supplies keys, the second supplies values, paired by index.
keys = ["name", "age", "city"]
values = ["Alice", 30, "London"]
person = dict(zip(keys, values))
print(person)You should see {'name': 'Alice', 'age': 30, 'city': 'London'}. See Python dictionary for more on dicts.
If lengths differ, normal zip() still stops at the shortest pair— you may get fewer keys than expected. Validate first when missing entries should fail:
dict(zip(keys, values, strict=True))Unzip a zipped object in Python
Python has no separate unzip() function. Use zip(*pairs) to split a sequence of tuples back into columns:
pairs = [("Alice", 90), ("Bob", 85), ("Charlie", 78)]
names, scores = zip(*pairs)
print(list(names))
print(list(scores))You should see ['Alice', 'Bob', 'Charlie'] and [90, 85, 78]. The * unpacks each tuple as a separate argument to zip().
Results are tuples; wrap with list() when you need lists. Unzipping consumes a one-pass iterator—materialize list(pairs) first if you need the original pairs again.
Use zip() with strings, tuples, ranges, and dictionaries
Strings — zip() pairs characters by position: list(zip("ab", "12")) → [('a', '1'), ('b', '2')].
Tuples — same behavior as lists.
range() — pair generated numbers with another sequence: list(zip(range(3), ["a", "b", "c"])). See Python range() for start, stop, and step rules.
Dictionaries — iterating a dict yields keys by default. To pair keys with values, dict.items() is usually clearer than zip(d, d.values()):
person = {"name": "Alice", "age": 30}
print(list(person.items()))Sets — zip() accepts sets, but sets are unordered. Do not rely on stable pairing when order matters.
zip() vs enumerate() vs map() vs zip_longest()
| Function | Use when |
|---|---|
zip() |
You need items from multiple iterables at the same position |
enumerate() |
You need index + item from one iterable |
map() |
You want to apply a function to items from one or more iterables |
zip_longest() |
You want to pair unequal-length iterables without dropping extra values |
zip() aligns parallel columns. enumerate() adds a counter to a single sequence. map() transforms; it can take multiple iterables like zip, but the goal is applying a function, not just pairing.
Practical scenarios where zip() is useful
Pair names with scores — for name, score in zip(names, scores): for reports or formatted output.
Build a dictionary from two lists — dict(zip(keys, values)) for config maps and labeled records.
Process two columns together — sum, multiply, or merge aligned fields: [a + b for a, b in zip(col_a, col_b)].
Compare expected and actual — for exp, got in zip(expected, actual): to spot mismatches row by row (use strict=True when lengths must match).
Combine multiple lists in one loop — zip(a, b, c) for three parallel sequences without nested indexing.
Transpose rows and columns — list(zip(*matrix)) turns row tuples into column tuples.
Unzip paired data — names, scores = zip(*pairs) to split CSV-like tuples into separate sequences.
Mistakes to avoid with zip()
- Expecting
zip()to return a list—it returns an iterator; uselist(zip(...))when you need all pairs at once. - Iterating the same zip object twice without saving
list(...)first—the second pass is empty. - Losing trailing data when lengths differ and not noticing truncation.
- Using plain
zip()whenstrict=Truewould catch a length bug in IDs, keys, or columns. - Zipping sets when order must be stable.
- Building
dict(zip(keys, values))from unequal lists without checking length—silent missing keys or values.
Summary
zip()pairs iterables by position and returns an iterator of tuples.- Wrap with
list(),tuple(), ordict(zip(keys, values))when you need a concrete collection. - By default, it stops at the shortest iterable—extras are dropped silently.
- Use
strict=Truewhen unequal length means bad data (Python 3.10+). - Use
itertools.zip_longest()when you must keep every value and pad shorter sequences. - Unzip with
zip(*pairs); for archive files, use zipfile, not built-inzip().

