Unpacking & Star Expressions

Reviewed & published by Brayan K

Unpacking is assigning the elements of an iterable to multiple variables at once — like a, b, c = [1, 2, 3] — and star expressions let one variable scoop up the rest.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

These destructuring tricks make code read like the data it handles: swapping values, splitting a list into head and tail, and spreading collections into function calls and merged dicts.

What You'll Learn in This Lesson

1 Basic Unpacking & the Famous Swap

Assign several variables from one iterable in a single line. The counts must match — and swapping values becomes a one-liner:

# Unpack a tuple or list into named variables
point = (3, 7)
x, y = point
print(x, y)              # 3 7

r, g, b = [255, 128, 0]
print(g)                 # 128

# The classic swap - no temp variable needed
a, b = 1, 2
a, b = b, a
print(a, b)              # 2 1

# Unpack right out of a loop
pairs = [("Ada", 88), ("Ben", 72)]
for name, score in pairs:
    print(f"{name}: {score}")
# Ada: 88
# Ben: 72

⭐ Star Expressions: Grab the Rest

A starred name scoops up however many items are left over into a list. It can go at the start, middle, or end:

nums = [1, 2, 3, 4, 5]

# Head and tail
first, *rest = nums
print(first)             # 1
print(rest)              # [2, 3, 4, 5]

# First, last, and everything between
first, *middle, last = nums
print(first, last)       # 1 5
print(middle)            # [2, 3, 4]

# Star can be empty if there's nothing left
a, b, *extra = [1, 2]
print(extra)             # []

# Ignore parts with _
name, *_ , year = ["Ada", "Lovelace", "math", 1815]
print(name, year)        # Ada 1815

Exactly one star is allowed per assignment. The starred variable always becomes a list, even when it captures a single item or nothing at all.

# Star expressions split a sequence into parts
scores = [95, 88, 72, 60, 100, 45]

top, *others = scores
print("Top:", top)            # Top: 95
print("Others:", others)      # Others: [88, 72, 60, 100, 45]

# Grab the ends, bundle the middle
opening, *body, closing = ["START", "a", "b", "c", "END"]
print("Open:", opening)       # Open: START
print("Body:", body)          # Body: ['a', 'b', 'c']
print("Close:", closing)      # Close: END

# Split a CSV-like row without slicing
row = "id,name,email,phone".split(",")
key, *fields = row
print(key)                    # id
print(fields)                 # ['name', 'email', 'phone']

# ✅ Expected output:
# Top: 95
# Others: [88, 72, 60, 100, 45]
# Open: START
# Body: ['a', 'b', 'c']
# Close: END
# id
# ['name', 'email', 'phone']

2 Spreading into Calls & Merging Dicts

The same star syntax works into a function call: * spreads an iterable as positional args, ** spreads a dict as keyword args:

def make(name, role, level):
    return f"{name} the {role} (lvl {level})"

# Spread a list as positional arguments
parts = ["Ada", "wizard", 9]
print(make(*parts))      # Ada the wizard (lvl 9)

# Spread a dict as keyword arguments
config = {"name": "Ben", "role": "knight", "level": 5}
print(make(**config))    # Ben the knight (lvl 5)

# Combine literal args with spreads
extra = ["mage", 3]
print(make("Cleo", *extra))   # Cleo the mage (lvl 3)
# Merge dicts with ** - later keys win on conflict
defaults = {"color": "black", "size": "M"}
overrides = {"size": "L", "trim": "gold"}

merged = {**defaults, **overrides}
print(merged)
# {'color': 'black', 'size': 'L', 'trim': 'gold'}

# Merge lists/tuples too
combined = [*[1, 2], *[3, 4]]
print(combined)          # [1, 2, 3, 4]

🪆 Nested Unpacking

Unpacking patterns can mirror nested structure, pulling apart tuples inside tuples in one go:

# Nested structure -> nested target
record = ("Ada", (1815, 12))
name, (year, month) = record
print(name, year, month)        # Ada 1815 12

# Unpack inside a loop over structured rows
data = [("p1", (10, 20)), ("p2", (30, 40))]
for label, (x, y) in data:
    print(f"{label}: x={x}, y={y}")
# p1: x=10, y=20
# p2: x=30, y=40

# Mix nesting with a star
head, *tail, (a, b) = [1, 2, 3, (9, 8)]
print(head, tail, a, b)         # 1 [2, 3] 9 8

The shape of the target on the left must match the shape of the data on the right. When it does, deeply structured data falls neatly into named variables.

🎯 Your Turn: Fill in the Blanks

Replace each ___ with the right unpacking target so the output matches.

# 🎯 YOUR TURN: replace each ___ to match the expected output
data = [10, 20, 30, 40, 50]

# 1. first item, then the rest as a list
___ = data
print(head)        # 10
print(rest)        # [20, 30, 40, 50]

# 2. swap these two with no temp
p, q = "x", "y"
___ = q, p
print(p, q)        # y x

# 3. merge two dicts (b should win on 'size')
a = {"color": "red", "size": "S"}
b = {"size": "L"}
merged = ___
print(merged)      # {'color': 'red', 'size': 'L'}

# Expected output:
# 10
# [20, 30, 40, 50]
# y x
# {'color': 'red', 'size': 'L'}
head, *rest = data          # 1. star captures the tail
p, q = q, p                 # 2. tuple swap
merged = {**a, **b}         # 3. b's 'size' wins

Common Errors & How to Fix Them

❌ Mismatched counts without a star

a, b = [1, 2, 3]   # ValueError: too many values to unpack

✅ Match the count exactly, or use a star: a, b, *rest = [1, 2, 3].

❌ Two stars in one assignment

*a, *b = [1, 2, 3]   # SyntaxError: two starred expressions

✅ Only one star is allowed per target. Python can't tell where one greedy group ends and the next begins.

❌ Expecting the star to give a tuple

first, *rest = (1, 2, 3)
print(type(rest))   # <class 'list'> - not a tuple!

✅ A starred target is always a list, even when unpacking a tuple. Convert with tuple(rest) if you need one.

🎯 Mini Challenge: Record Splitter

Use unpacking, stars, and a dict merge to process structured records.

# Mini Challenge: split records and merge configs
records = [
    ("Ada", 88, 91, 79),
    ("Ben", 70, 65, 80),
]

# Unpack the name, then star-capture all the scores
for name, *scores in records:
    print(f"{name}: avg {sum(scores) / len(scores):.1f}")

# Spread a list into a function call
def fmt(a, b, c):
    return f"{a}-{b}-{c}"

parts = ["x", "y", "z"]
print(fmt(*parts))

# Build a config from defaults + user overrides
defaults = {"theme": "light", "lang": "en"}
user = {"theme": "dark"}
print({**defaults, **user})

# Expected output:
# Ada: avg 86.0
# Ben: avg 71.7
# x-y-z
# {'theme': 'dark', 'lang': 'en'}

📚 Keep Learning

Go deeper with the official Python documentation:

Frequently Asked Questions

Lesson complete — destructuring mastered!

You can unpack tuples and lists, swap without a temp, scoop up the rest with a star, spread *args and **kwargs into calls, merge dicts with {**d1, **d2}, and destructure nested data.

Practice quiz

What does `a, b, c = [1, 2, 3]` do?

  • Assigns the whole list to a
  • Raises an error because you cannot unpack a list
  • Assigns 1 to a, 2 to b, and 3 to c
  • Assigns [1, 2, 3] to each variable

Answer: Assigns 1 to a, 2 to b, and 3 to c. Unpacking assigns each element of the iterable to the matching variable, as long as the counts match.

How do you swap two variables without a temporary variable?

  • a, b = b, a
  • swap(a, b)
  • a = b; b = a
  • a, b = a, b

Answer: a, b = b, a. a, b = b, a builds a tuple on the right and unpacks it on the left, swapping the values in one line.

After `first, *rest = [1, 2, 3, 4, 5]`, what is rest?

  • (2, 3, 4, 5)
  • 5
  • [1, 2, 3, 4, 5]
  • [2, 3, 4, 5]

Answer: [2, 3, 4, 5]. The starred name captures all leftover items into a list, so rest is [2, 3, 4, 5] and first is 1.

What type is the starred target, even when unpacking a tuple?

  • tuple
  • list
  • set
  • str

Answer: list. A starred target is always a list — e.g. first, *rest = (1, 2, 3) makes rest the list [2, 3]. Use tuple(rest) if you need a tuple.

How many starred targets are allowed in a single unpacking assignment?

  • Exactly one
  • Up to two
  • Any number
  • Zero — stars are not allowed

Answer: Exactly one. Only one star is allowed per target; `*a, *b = [1, 2, 3]` is a SyntaxError because Python cannot tell where one greedy group ends.

What does `make(*parts)` do when parts = ['Ada', 'wizard', 9]?

  • Passes the list as a single argument
  • Passes parts as keyword arguments
  • Spreads the list as positional arguments: make('Ada', 'wizard', 9)
  • Raises a TypeError

Answer: Spreads the list as positional arguments: make('Ada', 'wizard', 9). In a call, * unpacks an iterable into separate positional arguments, so make(*parts) is the same as make('Ada', 'wizard', 9).

What does `make(**config)` do when config = {'name': 'Ben', 'role': 'knight', 'level': 5}?

  • Passes the dict as one positional argument
  • Spreads the dict as keyword arguments: make(name='Ben', role='knight', level=5)
  • Passes only the dict's keys
  • Merges config into make

Answer: Spreads the dict as keyword arguments: make(name='Ben', role='knight', level=5). In a call, ** unpacks a dict into keyword arguments, so make(**config) is make(name='Ben', role='knight', level=5).

What is the result of `{**{'size': 'M', 'color': 'black'}, **{'size': 'L'}}`?

  • {'size': 'M', 'color': 'black'}
  • {'size': ['M', 'L'], 'color': 'black'}
  • A SyntaxError
  • {'size': 'L', 'color': 'black'}

Answer: {'size': 'L', 'color': 'black'}. When merging with **, later keys win on conflict, so the second dict's size 'L' overrides the first dict's 'M'.

What does `combined = [*[1, 2], *[3, 4]]` produce?

  • [[1, 2], [3, 4]]
  • [1, 2, 3, 4]
  • [1, 2], [3, 4]
  • [4, 3, 2, 1]

Answer: [1, 2, 3, 4]. The * spread expands each list inline, flattening them into a single list [1, 2, 3, 4].

Why does `a, b = [1, 2, 3]` raise an error?

  • Lists cannot be unpacked, only tuples can
  • Variables must be declared first
  • Too many values to unpack — counts must match (or use a star)
  • The list is too long for memory

Answer: Too many values to unpack — counts must match (or use a star). Without a star, the number of targets must equal the number of values. Use a, b, *rest = [1, 2, 3] when lengths differ.

Continue this course

Frequently asked questions

What is unpacking in Python?

Unpacking is assigning the elements of an iterable to several variables at once. Instead of grabbing items by index, you write 'a, b, c = [1, 2, 3]' and Python hands each value to the matching name. It works for any iterable — lists, tuples, strings, even the items of a dict — and makes code read like the data it describes.

What does the star (*) do in unpacking?

A starred name in an assignment, like 'first, *rest = items', captures all the leftover elements into a list. Exactly one star is allowed per target, and it can sit anywhere: 'first, *middle, last = items' grabs the ends and bundles everything between. It's the flexible way to split a sequence into 'this part' and 'the rest'.

How do *args and **kwargs work when CALLING a function?

When you call a function, * unpacks an iterable into positional arguments and ** unpacks a dict into keyword arguments. So f(*[1, 2]) is the same as f(1, 2), and f(**{'x': 1}) is the same as f(x=1). It's the mirror image of collecting *args/**kwargs in a definition — here you're spreading a collection back out into separate arguments.

How do I merge two dictionaries with **?

Use {**d1, **d2} to build a new dict containing all the pairs from both. If a key exists in both, the value from the later dict (d2) wins, because later keys overwrite earlier ones. From Python 3.9 you can also write d1 | d2, but the {**d1, **d2} spread works on every modern version and reads clearly.