How to Unpack Iterables Using Asterisk * in Python ?

Introduction

Python’s asterisk operator (*) is deceptively powerful. While beginners often encounter it first in the context of multiplication, it plays a much broader and more elegant role when unpacking iterables, collecting function arguments, and working with dynamic data structures.

In this article, we’ll explore how * and its double-star sibling ** are used to unpack and repack data in Python.

What Does "Unpacking" Mean?

Unpacking refers to extracting values from an iterable (like a list, tuple, set, or string) and assigning them to individual variables. It’s Python’s way of saying: “break this collection into parts and distribute them.”

There are two key types of unpacking:

  • Left-hand unpacking: Distribute elements into variables (a, *rest = iterable)
  • Function argument unpacking: Pass elements as arguments using * or **

Basic List/Tuple Unpacking with *

Python allows you to unpack values directly into variables, and the * operator makes this more flexible by collecting the "rest" of the items into a list.

1
2
3
4
a, b, *rest = [1, 2, 3, 4, 5]
print(a)      # 1
print(b)      # 2
print(rest)   # [3, 4, 5]

You can place the * at different positions:

1
2
3
4
5
6
*start, end = [10, 20, 30, 40]
print(start)  # [10, 20, 30]
print(end)    # 40

first, *middle, last = [1, 2, 3, 4, 5]
print(middle)  # [2, 3, 4]

⚠️ Only one starred target is allowed in any unpacking expression.

Basic Dictionary Unpacking with **

Python allows you to use the double asterisk ** to unpack dictionaries—a powerful feature for merging dictionaries or passing their contents into functions as named arguments.

Basic Example

1
2
3
4
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3}
merged_dict = {**dict1, **dict2}
print(merged_dict)

Output:

{'a': 1, 'b': 2, 'c': 3}

In this example:

  • **dict1 unpacks the contents of dict1 into key-value pairs.
  • **dict2 unpacks dict2 as well.
  • The result is a new dictionary with keys and values from both.

This is a clean and Pythonic way to combine dictionaries introduced in Python 3.5+.

Key Overwrite Behavior

If both dictionaries contain the same key, the last one wins—just like dict.update().

1
2
3
4
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 99, "c": 3}
merged = {**dict1, **dict2}
print(merged)

Output:

1
{'a': 1, 'b': 99, 'c': 3}

Here, the value of 'b' from dict2 overwrites the one from dict1.

Merging Multiple Dictionaries

You can merge more than two dictionaries at once:

1
2
3
4
5
d1 = {"x": 1}
d2 = {"y": 2}
d3 = {"z": 3}
combined = {**d1, **d2, **d3}
print(combined)

Output:

1
{'x': 1, 'y': 2, 'z': 3}

This works with any number of unpacked dictionaries.

Using with Dictionary Literals

You can even mix in unpacked dictionaries with new key-value pairs:

1
2
3
defaults = {"theme": "light", "font": "Arial"}
settings = {**defaults, "font": "Helvetica", "size": 12}
print(settings)

Output:

1
{'theme': 'light', 'font': 'Helvetica', 'size': 12}

Note how "font" is overwritten, and "size" is added.

Edge Cases and Validation

Unpacking with ** requires that all unpacked objects be dictionaries (or dict-like). If not, you’ll get a TypeError:

1
2
3
bad = [("a", 1), ("b", 2)]
# ❌ TypeError: 'list' object is not a mapping
# combined = {**bad}

To fix this, you must explicitly convert such structures:

1
2
valid = dict(bad)  # {'a': 1, 'b': 2}
combined = {**valid}

Comparison with update()

  • {**d1, **d2} creates a new dictionary.
  • d1.update(d2) modifies d1 in-place and returns None.

Code

1
2
3
4
5
6
7
d1 = {"a": 1}
d2 = {"b": 2}
# Non-destructive
merged = {**d1, **d2}

# In-place update (no return)
d1.update(d2)

This makes ** unpacking especially useful when you want immutability or are composing new configurations or payloads from several sources.

Using *args and **kwargs in Functions

Python functions can accept a variable number of arguments using:

  • *args: for positional arguments (packed as a tuple)
  • **kwargs: for keyword arguments (packed as a dictionary)

Example: *args

1
2
3
4
5
def greet(*names):
    for name in names:
        print(f"Hello, {name}!")

greet("Alice", "Bob", "Charlie")

Output:

1
2
3
Hello, Alice!
Hello, Bob!
Hello, Charlie!

Example: **kwargs

1
2
3
4
5
def show_info(**info):
    for key, value in info.items():
        print(f"{key}: {value}")

show_info(name="Dana", age=30, role="Engineer")

Output:

1
2
3
name: Dana
age: 30
role: Engineer

Unpacking in Function Calls

If you already have arguments in an iterable or dictionary, you can unpack them into a function using * and **.

1
2
3
4
5
def add(a, b, c):
    return a + b + c

nums = [1, 2, 3]
print(add(*nums))  # 6

For dictionaries, use ** to match named parameters:

1
2
3
4
5
def introduce(name, age):
    print(f"{name} is {age} years old.")

info = {'name': 'Dana', 'age': 30}
introduce(**info)

Merging and Copying Iterables with *

The * operator can be used to merge or shallow-copy lists and tuples.

Merging lists

1
2
3
4
list1 = [1, 2]
list2 = [3, 4]
merged = [*list1, *list2]
print(merged)  # [1, 2, 3, 4]

Shallow copying

1
copy_list = [*list1]

Merging dictionaries (with **)

1
2
3
4
dict1 = {'a': 1}
dict2 = {'b': 2}
merged_dict = {**dict1, **dict2}
print(merged_dict)  # {'a': 1, 'b': 2}

Unpacking in Loops

When working with structured data like tuples of different sizes, * allows you to capture variable-length parts:

1
2
3
4
5
6
7
data = [(1, 2, 3), (4, 5, 6)]
for a, *middle, c in data:
    print(middle)

# Output:
# [2]
# [5]

Here, *middle grabs whatever is in between a and c.

Unpacking Nested Structures

Unpacking works with nested data structures too. You can even combine * with normal tuple unpacking:

1
2
3
4
5
6
7
nested = [(1, (2, 3)), (4, (5, 6))]
for x, (y, z) in nested:
    print(x, y, z)

# Output:
# 1 2 3
# 4 5 6

If the inner tuple has variable length, use *:

1
2
3
4
5
6
7
nested = [(1, (2, 3, 4)), (5, (6, 7))]
for x, (y, *rest) in nested:
    print(f"x={x}, y={y}, rest={rest}")

# Output:
# x=1, y=2, rest=[3, 4]
# x=5, y=6, rest=[7]

Common Pitfalls

  • You can only use one starred expression on the left-hand side of an unpacking assignment:

    1
    2
    # ❌ Invalid
    # a, *b, *c = [1, 2, 3]
    
  • * and ** only work with iterables. Non-iterable objects will raise a TypeError.

  • When unpacking into functions, ensure the number of items matches the function’s signature—unless using *args or **kwargs.

Summary Table

Use Case Syntax Example
Assign extra items a, *b = [1, 2, 3]
Function with flexible args def f(*args):
Function with flexible keywords def f(**kwargs):
Unpack list to function f(*[1, 2, 3])
Unpack dict to function f(**{'x': 1, 'y': 2})
Merge lists [ *a, *b ]
Merge dicts { **d1, **d2 }
Loop unpacking with rest for a, *b, c in data:

Final Thoughts

The * and ** operators are essential tools for writing expressive and maintainable Python code. They simplify argument handling, data unpacking, merging structures, and working with complex or variable-length data.

Understanding how and when to use them will make your code not just shorter—but cleaner, clearer, and more Pythonic.