When people search for how to loop through two lists in Python or iterate two lists at the same time, they almost always want the same thing: walk the same index on each list together—first with first, second with second. That is “parallel” in the sense of aligned rows, not multiple CPU cores. The built-in zip() function is the standard answer: it takes two or more lists (any iterables), advances them in lockstep, and yields tuples you can unpack in a for loop.
This guide explains zip() for two lists, adding enumerate() when you need positions, using range() and indexes when that fits better, handling different list lengths (including itertools.zip_longest()), extending to more than two lists, and small recipes such as building a dict, summing corresponding items, and comparing values. It also contrasts zip with nested loops so you do not accidentally write an all-pairs search when you only wanted aligned pairs.
For single-list basics, see Python list with examples. For for syntax, see Python for loop. For zip in general, see Python zip function. For “index plus value” on one sequence, see Python enumerate.
Tested on: Python 3.13.3; kernel 6.14.0-37-generic.
Loop through two lists using zip()
The zip built-in is the idiomatic way to loop through two lists (or any iterables) in parallel. Each iteration receives one element from each argument in the same order; the length of the walk is driven by the shortest input, which matters later when lengths differ. In Python 3, zip returns an iterator: memory stays lazy until you consume it, and you can unpack each tuple into readable names.
Unpacking keeps the body of the loop focused on meaning (“name” and “score”) instead of subscripts:
names = ["Ada", "Lin", "Kim"]
scores = [92, 88, 95]
for name, score in zip(names, scores):
print(name, score)That prints three lines pairing each name with its score—the usual “python zip two lists” pattern in application code.
Loop through two lists with index using enumerate()
Sometimes you need the row number as well as the paired values—for logging, writing back to a third structure, or matching another table by position. Wrapping enumerate around zip gives you a zero-based index that stays in sync with each tuple from zip. The index counts successful pairings, not the original length of a longer list if zip truncated.
names = ["Ada", "Lin", "Kim"]
scores = [92, 88, 95]
for i, (name, score) in enumerate(zip(names, scores)):
print(i, name, score)That prints 0 Ada 92, then 1 Lin 88, then 2 Kim 95. If you only needed an index on one list, the plain enumerate patterns in Python enumerate apply; here zip keeps two streams aligned.
Loop through two lists using range() and index
Indexing with range(len(...)) is the older style and stays useful when you must assign back into the lists by position, combine non-default stepping, or mix lists with other index-driven logic. The important detail is to bound the loop by the smaller length (or the length you intend to touch) so you never read past the end of the shorter list. For how range stop values work, see Python range.
xs = [1, 2, 3]
ys = [10, 20, 30]
for i in range(len(xs)):
print(i, xs[i] + ys[i])That prints 0 11, 1 22, 2 33. If len(xs) != len(ys), use range(min(len(xs), len(ys))) or prefer zip / zip_longest so the control flow matches the data shape.
Loop through two lists with different lengths
Real data often has missing rows: one CSV has four keys and another has two labels. You must decide whether trailing items on the longer list should be ignored, surfaced with placeholders, or handled in a second pass.
What happens when zip() lists have different lengths
zip always stops when the shortest iterable is exhausted. Extra items on longer lists are not an error—they simply never reach your loop body, which is easy to miss in logs or tests.
a = [1, 2, 3, 4]
b = ["a", "b"]
for pair in zip(a, b):
print(pair)That prints (1, 'a') and (2, 'b') only—values 3 and 4 are skipped unless you handle length explicitly.
Use itertools.zip_longest() for missing values
When every index of the longest list should be visited, use itertools.zip_longest. It pairs elements until all iterators are exhausted, filling shorter streams with a fillvalue you choose (or None by default). Pick a fill value that cannot collide with real data, or branch on None inside the loop.
import itertools
a = [1, 2, 3, 4]
b = ["a", "b"]
for pair in itertools.zip_longest(a, b, fillvalue="-"):
print(pair)That yields four tuples, padding the shorter list with "-" after real values run out.
Loop through more than two lists
zip accepts any number of positional iterables. Each round produces one n-tuple aligned across all of them, still governed by the shortest sequence overall. That scales the same mental model from “two lists in Python” to three or more parallel columns—coordinates, RGB channels, or split CSV fields.
xs = [1, 2, 3]
ys = [10, 20, 30]
zs = [100, 200, 300]
for t in zip(xs, ys, zs):
print(sum(t))That prints 111, 222, 333. The same shortest-sequence rule applies: one short list truncates the whole zip.
Create a dictionary from two lists
When one list is keys and another is values in the same order, dict(zip(keys, values)) builds a mapping in one expression—no manual index loop required. Keys must be unique (later duplicates overwrite earlier ones, same as any dict literal). For broader mapping rules, see Python dictionary.
keys = ["x", "y", "z"]
vals = [1, 2, 3]
print(dict(zip(keys, vals)))That prints {'x': 1, 'y': 2, 'z': 3}.
Add corresponding items from two lists
Elementwise math is a common follow-up: totals, deltas, or vector addition. A for loop with zip is clear; a list comprehension stays readable when the body is a single expression. Both walk the same pairs; comprehension builds a new list without mutating the inputs.
a = [1, 2, 3]
b = [4, 5, 6]
sums = [x + y for x, y in zip(a, b)]
print(sums)That prints [5, 7, 9].
Compare items from two lists
Equality checks, thresholds, or custom predicates are all the same shape: pull aligned pairs, then test. If you only need a single yes/no answer such as “are all aligned pairs equal?” you can combine a generator with all or any; see Python any() and all() for that style.
a = [1, 4, 9]
b = [1, 3, 9]
for i, (x, y) in enumerate(zip(a, b)):
print(i, x == y)That prints 0 True, then 1 False, then 2 True on three lines (index followed by equality).
Nested loop vs zip(): important difference
A nested for over two lists visits the Cartesian product: every item from the first list paired with every item from the second—length roughly len(a) * len(b) iterations. zip visits at most min(len(a), len(b)) aligned pairs. Mixing these up is a frequent bug when someone says “compare two lists” but implements all-pairs logic by mistake.
xs = [1, 2]
ys = [10, 20]
pairs_zip = list(zip(xs, ys))
pairs_nested = [(x, y) for x in xs for y in ys]
print("zip", pairs_zip)
print("nested", pairs_nested)That prints zip [(1, 10), (2, 20)] and nested [(1, 10), (1, 20), (2, 10), (2, 20)].
Should you use parallel processing for list loops?
For aligned iteration with zip or indexes, work is already cheap and sequential; spinning up processes or threads rarely helps. Reserve multiprocessing or async for cases where each body step is genuinely expensive (heavy numeric kernels, blocking I/O) and you have profiled a bottleneck. This article’s “parallel lists” wording means iterate two lists simultaneously in the Python sense—row alignment—not automatic multi-core execution.
Common mistakes when looping through two lists
- Assuming
zipvisits every element of the longer list; it truncates at the shortest unless you usezip_longest. - Using
range(len(a))whenbis shorter and indexingb[i]without guarding the length; align bounds or usezip. - Confusing nested loops (all combinations) with
zip(same-index pairs only). - Forgetting that
zipin Python 3 returns an iterator—consume once, or wrap inlist(zip(...))if you need multiple passes or random access.
Python loop through lists quick reference table
| Goal | Pattern |
|---|---|
| Pair two lists | for x, y in zip(a, b): |
| Index + two values | for i, (x, y) in enumerate(zip(a, b)): |
| Manual index | for i in range(min(len(a), len(b))): |
| Unequal length, pad | itertools.zip_longest(a, b, fillvalue=...) |
| Three or more aligned | for row in zip(a, b, c): |
| Keys + values → dict | dict(zip(keys, vals)) |
| All combinations | nested loops or itertools.product |
Summary
To loop through two lists in Python, start with zip() for aligned tuples, add enumerate() when you need a running index, and use range() plus subscripts when you mutate by position or need non-default stepping—always bound indexes by the shorter list unless you pad. Different lengths mean either accepting zip truncation or switching to itertools.zip_longest() with an explicit fillvalue. dict(zip(...)), sums, and comparisons are the same pairing idea with a small transformation after each step. Nested loops answer a different question (every combination), not row alignment. Multiprocessing is unrelated to everyday zip workflows unless each iteration is truly expensive and measured as such.

