How Python zip Handles Unequal Length Iterables

This article explains how Python's built-in zip() function behaves when supplied with iterables of different lengths. By default, zip() stops pairing elements as soon as the shortest input is exhausted, silently ignoring any remaining items. You will learn about this default truncation behavior, how to prevent silent bugs using the strict=True parameter introduced in Python 3.10, and how to preserve all values using the standard library's itertools.zip_longest() function.

Default Behavior: Silent Truncation

By default, zip() pairs elements from each iterable sequentially based on their index. When the provided iterables have unequal lengths, zip() terminates immediately when the shortest iterable runs out of elements. Any remaining elements in the longer iterables are omitted without warning.

letters = ["a", "b", "c", "d"]
numbers = [1, 2]

result = list(zip(letters, numbers))
print(result)
# Output: [('a', 1), ('b', 2)]

In the example above, 'c' and 'd' are discarded. Because this occurs without an error or warning, it can lead to silent bugs when the data sources are expected to be of identical size.

Enforcing Equal Lengths: strict=True

In Python 3.10 and later, zip() includes an optional boolean parameter called strict. When set to strict=True, zip() verifies that all iterables are of equal length. If one iterable ends before another, the function raises a ValueError.

letters = ["a", "b", "c", "d"]
numbers = [1, 2]

try:
    result = list(zip(letters, numbers, strict=True))
except ValueError as e:
    print(f"Error: {e}")
# Output: Error: zip() argument 2 is shorter than argument 1

Using strict=True is best practice whenever input collections are required to correspond one-to-one, as it immediately surfaces mismatched data lengths.

Keeping All Elements: itertools.zip_longest()

If the goal is to process every element regardless of iterable length, use zip_longest() from the built-in itertools module. Instead of stopping at the shortest iterable, it iterates until the longest iterable is exhausted, filling in missing slots with a placeholder.

from itertools import zip_longest

letters = ["a", "b", "c", "d"]
numbers = [1, 2]

# Missing values default to None
result = list(zip_longest(letters, numbers))
print(result)
# Output: [('a', 1), ('b', 2), ('c', None), ('d', None)]

# Custom placeholder using fillvalue
result_custom = list(zip_longest(letters, numbers, fillvalue=0))
print(result_custom)
# Output: [('a', 1), ('b', 2), ('c', 0), ('d', 0)]

Summary of Options