New: 6 free SQL practice datasets with 300+ questions — try the SQL Compiler →
Python

Python *args and **kwargs Explained

Python *args and **kwargs explained: accept any number of arguments, unpack lists and dicts in calls, and keyword-only arguments, with real code and output.

Upskly AI Team September 26, 2026 7 min read
Python *args and **kwargs Explained

*args and **kwargs in Python let a function accept a flexible number of arguments. *args collects any extra positional arguments into a tuple. **kwargs collects any extra keyword arguments (name=value) into a dictionary. The names args and kwargs are just convention. The stars are what matter.

The same stars also work in reverse when you call a function, to spread a list or dictionary into arguments. This guide covers both directions, with real output.

In this guide

The short version

  • def f(*args) – args is a tuple of the extra positional arguments.
  • def f(**kwargs) – kwargs is a dict of the extra keyword arguments.
  • f(*items) and f(**options) in a call unpack a list or dict into arguments.
  • Order in the definition: normal parameters, then *args, then keyword-only ones, then **kwargs.

Why do we need them?

A normal function has a fixed number of parameters. Give it more and Python refuses:

def add(a, b):
    return a + b

print(add(1, 2, 3))

Output

TypeError: add() takes 2 positional arguments but 3 were given

Sometimes you genuinely do not know how many values will arrive: a function that adds any amount of numbers, or one that passes options through to something else. That is what the stars are for.

*args: any number of positional arguments

Put * before a parameter name and Python gathers all the extra positional arguments into a tuple under that name. It can hold zero, one or a hundred values:

def total(*args):
    print(args, type(args).__name__)
    return sum(args)

print(total(1, 2, 3))
print(total())

Output

(1, 2, 3) tuple
6
() tuple
0

Because args is an ordinary tuple, you can loop over it, index it, or pass it to sum(), as here.

**kwargs: any number of keyword arguments

Put ** before a name and the extra keyword arguments arrive as a dictionary, with the argument names as keys:

def show(**kwargs):
    print(kwargs, type(kwargs).__name__)
    for key, value in kwargs.items():
        print(key, "=", value)

show(name="Asha", city="Pune")

Output

{'name': 'Asha', 'city': 'Pune'} dict
name = Asha
city = Pune

You can read it with .items() or .get() like any dictionary. See Python Dictionaries Explained if you need a refresher.

Using both, and the order rule

You can use both in the same function, along with normal parameters. The order in the definition is fixed: normal parameters first, then *args, then any keyword-only parameters, then **kwargs:

def report(title, *args, sep="-", **kwargs):
    print(title, args, sep, kwargs)

report("Sales", 10, 20, sep="|", region="North", year=2026)

Output

Sales (10, 20) | {'region': 'North', 'year': 2026}

Here title took "Sales", args took the two numbers, sep was matched by name, and kwargs caught the rest.

The stars in a function call

The stars also work when you call a function. * spreads a list or tuple into separate positional arguments, and ** spreads a dictionary into keyword arguments. The same syntax merges lists and dictionaries into new ones:

def volume(l, w, h):
    return l * w * h

dims = [2, 3, 4]
print(volume(*dims))
opts = {"l": 2, "w": 3, "h": 5}
print(volume(**opts))
a = [1, 2]
b = [3, 4]
print([*a, *b])
print({**{"x": 1}, **{"y": 2}})

Output

24
30
[1, 2, 3, 4]
{'x': 1, 'y': 2}

Remember the split: in a definition the stars collect values, and in a call they spread values.

Keyword-only arguments

A bare * in the parameter list means “everything after this must be passed by name”. This stops callers from mixing up values that are easy to confuse, like a port and a flag:

def connect(host, *, port=80, secure=False):
    return f"{host}:{port} secure={secure}"

print(connect("example.com", port=443, secure=True))

Output

example.com:443 secure=True

Passing 443 by position is refused:

def connect(host, *, port=80, secure=False):
    return f"{host}:{port}"

print(connect("example.com", 443))

Output

TypeError: connect() takes 1 positional argument but 2 were given

Where you see them in real code

The most common use is a wrapper that has to accept whatever the wrapped function accepts, and pass it along unchanged. Decorators are built this way (we cover them in Python Decorators Explained Step by Step):

def logged(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__, args, kwargs)
        return func(*args, **kwargs)
    return wrapper

@logged
def add(a, b=0):
    return a + b

print(add(2, b=5))

Output

calling add (2,) {'b': 5}
7

The wrapper does not need to know that add takes a and b. It collects everything with *args, **kwargs and forwards it with the same stars.

You will also see **kwargs in library functions that accept many optional settings, and in classes that pass arguments up to a parent class.

Common mistakes

Mistake 1: passing a list when you meant to spread it

total(nums) passes one argument, the whole list, so args becomes ([1, 2, 3],) and the sum fails:

def total(*args):
    return sum(args)

nums = [1, 2, 3]
print(total(nums))

Output

TypeError: unsupported operand type(s) for +: 'int' and 'list'

Add a star in the call to spread the list into separate arguments:

def total(*args):
    return sum(args)

nums = [1, 2, 3]
print(total(*nums))

Output

6

Mistake 2: the same argument twice

Giving a value both by position and by name is an error:

def f(a):
    return a

print(f(1, a=2))

Output

TypeError: f() got multiple values for argument 'a'

Mistake 3: overusing them

*args and **kwargs hide what a function really accepts, so readers and editors cannot help you. Use them when the flexibility is real, and use clear named parameters the rest of the time.

Cheat sheet

The star syntax
SyntaxWhereWhat it does
*argsIn a defCollect extra positional arguments into a tuple
**kwargsIn a defCollect extra keyword arguments into a dict
*itemsIn a callSpread a list or tuple into positional arguments
**optionsIn a callSpread a dict into keyword arguments
def f(a, *, b)In a defb must be passed by name
[*a, *b]In an expressionMerge two lists
{**a, **b}In an expressionMerge two dicts (b wins on clashes)

Try it yourself

Work out each answer first, then open the solution.

1. Write average(*nums) that returns the mean of any number of values.

Show solution
def average(*nums):
    return sum(nums) / len(nums)

print(average(2, 4, 6))

Output

4.0

2. Write make_tag(tag, **attrs) so that make_tag("a", href="/home", title="Home") builds an HTML opening tag.

Show solution
def make_tag(tag, **attrs):
    parts = " ".join(f'{k}="{v}"' for k, v in attrs.items())
    return f"<{tag} {parts}>"

print(make_tag("a", href="/home", title="Home"))

Output

<a href="/home" title="Home">

The dictionary of attributes is turned into key="value" pieces and joined with spaces.

3. What does print(*[1, 2, 3], sep="-") print?

Show answer

Output

1-2-3

The star spreads the list into three separate arguments, and sep sets what goes between them.

4. What does this print?

def f(*args, **kwargs):
    return len(args), len(kwargs)

print(f(1, 2, x=3))
Show answer

Output

(2, 1)

The two numbers went to args and the one keyword argument went to kwargs.

Try them in our free Python compiler.

Frequently asked questions

What does *args mean in Python?

It collects any extra positional arguments passed to a function into a tuple. The name args is just convention. It is the single star that does the work.

What does **kwargs mean in Python?

It collects any extra keyword arguments (name=value) into a dictionary. Again, kwargs is convention, and the double star is what matters.

Can I use *args and **kwargs together?

Yes. The order is: normal parameters, *args, keyword-only parameters, then **kwargs.

What is the difference between * and ** in a function call?

* spreads a list or tuple into positional arguments. ** spreads a dictionary into keyword arguments, using the keys as argument names.

Is *args a list or a tuple?

A tuple. If you need a list, convert it with list(args).

When should I avoid *args and **kwargs?

When the function has a clear, fixed set of inputs. Named parameters document themselves and let tools catch mistakes, while *args and **kwargs hide what is accepted.

Run this code in your browser

The free Upskly compiler runs Python with nothing to install. Paste the example, change it and see what happens.

Open Python Compiler

Stuck on a traceback?

AI Assist works inside the Python notebook, so you can ask about an error or a concept without leaving the cell.

Try AI Assist

Test yourself

Timed questions on Functions, Scope and Closures, with an explanation for every answer.

Take the Quiz
Upskly AI Team
Learning made simple
Scroll to Top