Python - Zip Two Lists Together

The `zip()` function takes two or more iterables and returns an iterator of tuples, where each tuple contains elements from the same position across all input iterables.

Key Insights

  • Python’s zip() function combines multiple iterables element-wise, creating tuples that pair corresponding elements from each input sequence
  • Zipping lists enables parallel iteration, dictionary creation from separate key-value lists, and elegant solutions for transposing data structures
  • Understanding zip behavior with unequal-length lists and memory-efficient iteration patterns prevents common pitfalls in production code

Basic Zip Operations

The zip() function takes two or more iterables and returns an iterator of tuples, where each tuple contains elements from the same position across all input iterables.

names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]

zipped = zip(names, ages)
print(list(zipped))
# Output: [('Alice', 25), ('Bob', 30), ('Charlie', 35)]

The result is an iterator, not a list. Convert to a list when you need to reuse the data or inspect it during debugging. For one-time iteration, use the iterator directly to save memory:

names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]

for name, age in zip(names, ages):
    print(f"{name} is {age} years old")
# Output:
# Alice is 25 years old
# Bob is 30 years old
# Charlie is 35 years old

Creating Dictionaries from Two Lists

One of the most practical applications is constructing dictionaries when you have separate lists for keys and values:

keys = ['username', 'email', 'role']
values = ['admin', 'admin@example.com', 'administrator']

user_dict = dict(zip(keys, values))
print(user_dict)
# Output: {'username': 'admin', 'email': 'admin@example.com', 'role': 'administrator'}

This pattern appears frequently when parsing configuration files or processing API responses where keys and values arrive separately:

# Simulating CSV header and row data
headers = ['id', 'product', 'price', 'quantity']
row = ['101', 'Laptop', '999.99', '5']

product = dict(zip(headers, row))
print(product)
# Output: {'id': '101', 'product': 'Laptop', 'price': '999.99', 'quantity': '5'}

Handling Unequal Length Lists

When lists have different lengths, zip() stops at the shortest list by default:

list1 = [1, 2, 3, 4, 5]
list2 = ['a', 'b', 'c']

result = list(zip(list1, list2))
print(result)
# Output: [(1, 'a'), (2, 'b'), (3, 'c')]

For Python 3.10+, use zip(strict=True) to raise an exception when lengths differ:

list1 = [1, 2, 3, 4, 5]
list2 = ['a', 'b', 'c']

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

For earlier Python versions or when you need all elements, use itertools.zip_longest():

from itertools import zip_longest

list1 = [1, 2, 3, 4, 5]
list2 = ['a', 'b', 'c']

result = list(zip_longest(list1, list2, fillvalue=None))
print(result)
# Output: [(1, 'a'), (2, 'b'), (3, 'c'), (4, None), (5, None)]

Specify a custom fill value for missing elements:

from itertools import zip_longest

prices = [10.99, 15.99, 20.99]
discounts = [0.1, 0.15]

result = list(zip_longest(prices, discounts, fillvalue=0.0))
print(result)
# Output: [(10.99, 0.1), (15.99, 0.15), (20.99, 0.0)]

Unzipping Lists

The zip() function is its own inverse when combined with the unpacking operator:

pairs = [('Alice', 25), ('Bob', 30), ('Charlie', 35)]

names, ages = zip(*pairs)
print(names)   # Output: ('Alice', 'Bob', 'Charlie')
print(ages)    # Output: (25, 30, 35)

This returns tuples, not lists. Convert explicitly if needed:

pairs = [('Alice', 25), ('Bob', 30), ('Charlie', 35)]

names, ages = map(list, zip(*pairs))
print(names)   # Output: ['Alice', 'Bob', 'Charlie']
print(ages)    # Output: [25, 30, 35]

Zipping Multiple Lists

zip() accepts any number of iterables:

ids = [1, 2, 3]
names = ['Alice', 'Bob', 'Charlie']
departments = ['Engineering', 'Sales', 'Marketing']
salaries = [120000, 80000, 95000]

employees = list(zip(ids, names, departments, salaries))
for emp in employees:
    print(emp)
# Output:
# (1, 'Alice', 'Engineering', 120000)
# (2, 'Bob', 'Sales', 80000)
# (3, 'Charlie', 'Marketing', 95000)

Combine with dictionary comprehension for structured data:

ids = [1, 2, 3]
names = ['Alice', 'Bob', 'Charlie']
salaries = [120000, 80000, 95000]

employees = {
    emp_id: {'name': name, 'salary': salary}
    for emp_id, name, salary in zip(ids, names, salaries)
}
print(employees)
# Output: {1: {'name': 'Alice', 'salary': 120000}, 
#          2: {'name': 'Bob', 'salary': 80000}, 
#          3: {'name': 'Charlie', 'salary': 95000}}

Matrix Transposition

Transpose a matrix (list of lists) using zip with unpacking:

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

transposed = list(zip(*matrix))
print(transposed)
# Output: [(1, 4, 7), (2, 5, 8), (3, 6, 9)]

Convert back to lists of lists:

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

transposed = [list(row) for row in zip(*matrix)]
print(transposed)
# Output: [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

Parallel Iteration with Enumerate

Combine zip() with enumerate() for indexed parallel iteration:

questions = ['What is your name?', 'What is your quest?', 'What is your favorite color?']
answers = ['Arthur', 'To seek the Holy Grail', 'Blue']

for i, (question, answer) in enumerate(zip(questions, answers), start=1):
    print(f"Q{i}: {question}")
    print(f"A{i}: {answer}\n")
# Output:
# Q1: What is your name?
# A1: Arthur
#
# Q2: What is your quest?
# A2: To seek the Holy Grail
#
# Q3: What is your favorite color?
# A3: Blue

Aggregating Data from Multiple Sources

Process data from multiple lists simultaneously:

product_ids = ['P001', 'P002', 'P003']
quantities = [10, 5, 8]
unit_prices = [29.99, 49.99, 19.99]

total_value = sum(qty * price for qty, price in zip(quantities, unit_prices))
print(f"Total inventory value: ${total_value:.2f}")
# Output: Total inventory value: $659.75

# Detailed breakdown
for pid, qty, price in zip(product_ids, quantities, unit_prices):
    line_total = qty * price
    print(f"{pid}: {qty} units × ${price} = ${line_total:.2f}")
# Output:
# P001: 10 units × $29.99 = $299.90
# P002: 5 units × $49.99 = $249.95
# P003: 8 units × $19.99 = $159.92

Memory Efficiency Considerations

zip() returns an iterator that generates values on-demand. This matters for large datasets:

# Memory efficient - processes one pair at a time
large_list1 = range(1000000)
large_list2 = range(1000000)

for a, b in zip(large_list1, large_list2):
    result = a + b
    # Process without loading entire result set into memory

Avoid converting to list unless necessary:

# Inefficient - loads everything into memory
result = list(zip(range(1000000), range(1000000)))

# Efficient - iterate directly
for pair in zip(range(1000000), range(1000000)):
    process(pair)

The zip() function provides elegant solutions for parallel data processing. Use it to combine related data, create dictionaries from separate key-value sources, transpose matrices, and iterate over multiple sequences simultaneously. Remember that zip() returns an iterator for memory efficiency and stops at the shortest input by default—use strict=True or zip_longest() when you need different behavior.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.