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
- Tuple and list unpacking into variables
- Swapping values with no temp variable
- Star expressions: a, *rest = ...
- Spreading *args and **kwargs into calls
- Merging dicts with {**d1, **d2}
- Nested unpacking for structured data
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 1815Exactly 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 8The 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' winsCommon 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:
- Tutorial — Tuples & Sequence Unpacking — the basics of unpacking sequences.
- Language Reference — Calls (* and ** in calls) — how spreading arguments into a call works.
- PEP 3132 — Extended Iterable Unpacking — the proposal that introduced the starred target.
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
- Previous: Structural Pattern Matching (match/case)
- Next: Sorting with sorted() & key — Order data with sorted/sort, reverse, and key functions (multi-key too)
- Quick reference: Python cheat sheet › Lists & Comprehensions
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.