Skip to content

Python · Comprehensions & Iteration

Python enumerate, zip and Unpacking Explained — Indexes, Pairing, and Star Args

4 min read Updated 2026-06-19 Share:

Practice enumerate, zip & Unpacking interview questions

enumerate, zip, and unpacking, explained

These tools replace clunky index-juggling with clear, Pythonic loops. Reaching for range(len(...)) in an interview is a small tell; enumerate and zip are what experienced Python developers use. This guide covers both, plus the star-unpacking tricks that go with them.

enumerate — index and value together

When you need the index and the item, use enumerate instead of range(len(...)):

for i, name in enumerate(["a", "b", "c"]):
    print(i, name)        # 0 a / 1 b / 2 c

# the un-Pythonic version it replaces:
for i in range(len(names)):
    print(i, names[i])

Set a different starting index with start:

for rank, name in enumerate(["a", "b"], start=1):
    print(rank, name)     # 1 a / 2 b

zip — iterate multiple sequences in lockstep

zip pairs up items from several iterables, yielding tuples until the shortest runs out:

names = ["Ada", "Alan"]
ages = [36, 41]
for name, age in zip(names, ages):
    print(name, age)      # Ada 36 / Alan 41

Because it stops at the shortest, extra items in a longer iterable are silently dropped:

list(zip([1, 2, 3], ["a", "b"]))   # [(1, 'a'), (2, 'b')] — the 3 is lost

zip_longest when lengths differ

If you'd rather pad to the longest, use itertools.zip_longest:

from itertools import zip_longest
list(zip_longest([1, 2, 3], ["a"], fillvalue="?"))
# [(1, 'a'), (2, '?'), (3, '?')]

Unzipping with zip(*)

The same zip "transposes" back when you unpack a sequence of pairs with *:

pairs = [(1, "a"), (2, "b"), (3, "c")]
nums, letters = zip(*pairs)
nums         # (1, 2, 3)
letters      # ('a', 'b', 'c')

zip(*pairs) feeds each pair as a separate argument, so zip lines up all the firsts, then all the seconds.

Building a dict from two sequences

zip plus dict is the idiomatic way to combine keys and values:

keys = ["x", "y", "z"]
vals = [1, 2, 3]
dict(zip(keys, vals))     # {'x': 1, 'y': 2, 'z': 3}

Extended (star) unpacking

The * operator captures "the rest" into a list during unpacking — useful for splitting off the head or tail:

first, *rest = [1, 2, 3, 4]      # first=1, rest=[2, 3, 4]
*init, last = [1, 2, 3, 4]       # init=[1, 2, 3], last=4
a, *mid, z = [1, 2, 3, 4, 5]     # a=1, mid=[2, 3, 4], z=5

Only one starred name is allowed per unpacking, and it always collects into a list.

Star args in a call

The flip side: * in a call spreads an iterable into positional arguments, and ** spreads a dict into keyword arguments:

def point(x, y, z):
    return (x, y, z)

coords = [1, 2, 3]
point(*coords)              # same as point(1, 2, 3)

kwargs = {"x": 1, "y": 2, "z": 3}
point(**kwargs)             # same as point(x=1, y=2, z=3)

Recap

Use enumerate (with an optional start) instead of range(len(...)) when you need indexes, and zip to walk several iterables in lockstep — it stops at the shortest, so use itertools.zip_longest to pad. zip(*pairs) unzips, and dict(zip(keys, vals)) builds a mapping. Star unpacking (first, *rest = ...) splits a sequence into head and tail, while * and ** in a call spread an iterable or dict into arguments.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel