** in Python is one symbol with two unrelated jobs: between numbers it's the exponentiation operator (2 ** 3 == 8); in front of a dictionary or in a function signature, it means unpacking — spreading a dict's key-value pairs into arguments, or collecting arbitrary keyword arguments into one. Context determines which behavior you get, and conflating the two is a common source of confusion for developers moving past basic syntax.
Below is every context ** shows up in, with the operator precedence, return-type, and merging gotchas you need to use it correctly.
Definition: base ** exponent raises base to the power of exponent and returns the result using Python's built-in arithmetic rules.
result = 2 ** 10
print(result)
# 1024 — standard exponentiation, int ** int returns int
The python power operator binds tighter than almost every other arithmetic operator, including unary minus. This trips people up constantly:
value = -2 ** 2
print(value)
# -4, not 4 — ** evaluates before unary minus is applied
# equivalent to -(2 ** 2)
value_correct = (-2) ** 2
print(value_correct)
# 4 — parentheses force the negation first
** is also right-associative, unlike most binary operators:
chained = 2 ** 3 ** 2
print(chained)
# 512, not 64 — evaluates as 2 ** (3 ** 2), i.e. 2 ** 9
Key takeaway: ** has higher precedence than unary minus and is right-associative, so -2 ** 2 and 2 ** 3 ** 2 rarely evaluate the way you'd guess at a glance.
Definition: All three compute exponentiation, but they differ in what types they accept and what type they return — and that difference matters more than the syntax choice.
** operatorpow() builtinmath.pow()Syntaxa ** bpow(a, b)math.pow(a, b)Return type (int, int)intintfloat (always)Supports moduloNoYes — pow(a, b, mod)NoAccepts negative exponentsYes (returns float)Yes (returns float)YesImport requiredNoNoimport mathprint(2 ** 3) # 8 (int)
print(pow(2, 3)) # 8 (int)
print(math.pow(2, 3)) # 8.0 (float) — math.pow always returns float
print(2 ** -1) # 0.5 — negative exponent forces float
print(pow(2, 3, 5)) # 3 — modular exponentiation: (2**3) % 5, unique to pow()
math.pow() always returns a float, even for clean integer results. If you need modular exponentiation (cryptography, hashing, competitive programming), pow(a, b, mod) is dramatically faster than a ** b followed by % mod, because it never builds the full intermediate value.
Key takeaway: Use ** or pow() to stay in int, use math.pow() only when you explicitly want a float, and use pow(a, b, mod) for modular exponentiation.
Definition: Inside a dict literal, ** unpacks another dictionary's key-value pairs in place — this is the core mechanism behind dictionary unpacking in Python and the fastest built-in way to merge dicts.
defaults = {"theme": "light", "timeout": 30}
overrides = {"timeout": 60, "retries": 3}
merged = {**defaults, **overrides}
print(merged)
# {'theme': 'light', 'timeout': 60, 'retries': 3}
# later keys win — overrides.timeout (60) replaces defaults.timeout (30)
Order matters: in a conflict, whichever dict is unpacked last wins.
a = {"x": 1}
b = {"x": 2}
print({**a, **b}) # {'x': 2} — b overwrites a
print({**b, **a}) # {'x': 1} — a overwrites b
For Python 3.9+, the | merge operator does the same thing (defaults | overrides), but {**d1, **d2} remains the portable syntax that works on 3.5+.
Key takeaway: {**d1, **d2} merges dictionaries with last-unpacked-wins conflict resolution, and it's the most widely compatible merge syntax across Python 3 versions.
Definition: In a function signature, **kwargs collects any keyword arguments not explicitly named in the parameter list into a single dict — this is what python kwargs refers to.
def create_user(name, **kwargs):
print(f"Name: {name}")
print(f"Extra fields: {kwargs}")
create_user("Bob", age=25, role="editor")
# Name: Bob
# Extra fields: {'age': 25, 'role': 'editor'}
# kwargs is a plain dict containing every keyword arg not matched to a named parameter
kwargs is a convention, not a keyword — the mechanism is the ** prefix:
def log_event(event_name, **metadata):
print(event_name, metadata)
log_event("purchase", user_id=42, amount=19.99)
# purchase {'user_id': 42, 'amount': 19.99}
**kwargs must come after *args and after any positional or keyword-only parameters — Python enforces this ordering with a SyntaxError before the function ever runs.
Key takeaway: **kwargs gives a function an open-ended way to accept keyword arguments it doesn't explicitly define, packaged as a regular dict inside the function body.
Definition: The inverse of **kwargs — calling a function with **some_dict unpacks that dict's keys as named arguments at the call site.
def connect(host, port, timeout=5):
print(f"Connecting to {host}:{port}, timeout={timeout}")
config = {"host": "db.internal", "port": 5432, "timeout": 10}
connect(**config)
# Connecting to db.internal:5432, timeout=10
# each key in config is matched to a parameter name
The dict's keys must match the function's parameter names exactly, or you get a TypeError:
bad_config = {"host": "db.internal", "wrong_key": 5432}
connect(**bad_config)
# TypeError: connect() missing 1 required positional argument: 'port'
Key takeaway: **dict at a call site unpacks keys as named arguments, and every key must exactly match a parameter name or the call fails with a TypeError.
Definition: You can chain more than two ** unpacks in a single literal or call, and Python resolves conflicts left to right.
base = {"env": "prod", "debug": False}
feature_flags = {"debug": True, "new_ui": True}
user_overrides = {"new_ui": False}
final_config = {**base, **feature_flags, **user_overrides}
print(final_config)
# {'env': 'prod', 'debug': True, 'new_ui': False}
# user_overrides.new_ui (False) wins because it's unpacked last
The same chaining works in function calls, but with a key difference — collisions there raise instead of silently overwriting:
def build(**kwargs):
return kwargs
print(build(**{"a": 1}, **{"b": 2}))
# {'a': 1, 'b': 2} — fine, no overlapping keys
print(build(**{"a": 1}, **{"a": 99}))
# TypeError: build() got multiple values for keyword argument 'a'
Key takeaway: Chaining ** unpacks resolves key conflicts left-to-right in dict literals, but the identical conflict at a function call site raises a TypeError instead of silently overwriting.
1. Config merging (defaults + environment + overrides)
DEFAULTS = {"log_level": "INFO", "retries": 3}
ENV_CONFIG = {"log_level": "DEBUG"}
runtime_config = {**DEFAULTS, **ENV_CONFIG}
# {'log_level': 'DEBUG', 'retries': 3} — environment wins over defaults
2. Function wrappers / decorators that pass through arbitrary arguments
def timed(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time() - start:.4f}s")
return result
return wrapper
# **kwargs here lets the wrapper support ANY wrapped function's signature
3. Building API request parameters from optional fields
def build_params(**filters):
return {k: v for k, v in filters.items() if v is not None}
params = build_params(status="active", category=None, limit=50)
# {'status': 'active', 'limit': 50} — None values dropped before hitting the API
4. Updating dataclass-like objects immutably
from dataclasses import dataclass, asdict
@dataclass
class User:
name: str
role: str
original = User(name="Alice", role="viewer")
updated = User(**{**asdict(original), "role": "admin"})
# User(name='Alice', role='admin') — unpack, override one field, rebuild
Key takeaway: Config layering, pass-through wrappers, clean API params, and immutable object updates cover the vast majority of ** usage in real codebases.
What does ** mean in Python?
** has two meanings depending on context: between two numbers it's the exponentiation operator (3 ** 2 == 9); before a dictionary or in a function signature it means unpacking, either spreading a dict's keys as arguments or collecting keyword arguments into a dict.
Is ** the same as pow() in Python?
Not exactly. ** and pow() both return an int for integer inputs and behave the same for basic exponentiation, but only pow() supports a third argument for modular exponentiation (pow(a, b, mod)), which ** cannot do.
Why does math.pow() return a float when ** returns an int?
math.pow() is a thin wrapper around the C standard library's pow() function, which always operates on and returns floating-point numbers, regardless of input type. ** and the pow() builtin preserve int results when both operands are integers.
How do you merge two dictionaries in Python using **?
Use {**dict1, **dict2} inside a new dict literal. Keys from dict2 overwrite matching keys from dict1 because it's unpacked second — the dict unpacked last always wins on conflicts.
What is the difference between *args and **kwargs?
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. Both let a function accept an arbitrary number of arguments, but they capture different argument types and must appear in that order in a function signature (*args before **kwargs).
Can you unpack a dictionary into function arguments?
Yes. Calling my_function(**my_dict) unpacks each key in my_dict as a named argument, provided every key matches a parameter name in the function's signature exactly. A mismatched key raises a TypeError for an unexpected keyword argument.
Does ** have higher precedence than unary minus in Python?
Yes. -2 ** 2 evaluates to -4, not 4, because ** binds before the unary minus is applied — it's equivalent to -(2 ** 2). Use parentheses, (-2) ** 2, to force the negation first.
Learn what the double asterisk (**) means in Python, from exponentiation with the Python power operator to dictionary unpacking and **kwargs. This guide explains operator precedence, the differences between **, pow(), and math.pow(), how to merge dictionaries, and how to pass keyword arguments to functions. Explore practical examples and common pitfalls, including duplicate keys and argument conflicts.
