Python Parameters Decoded: From Confusion to Clarity

Anik SikderAnik Sikder
4 min read

(aka: The Art of Talking With Your Functions Without Losing Your Mind)

Functions are like little wizards in Python. You hand them some ingredients, they stir their magical cauldron, and-poof-you get a result. But here’s the catch: if you don’t know how to hand over those ingredients correctly, your wizard might either mess up the potion or throw a TypeError tantrum.

So today, we’re going on a deep dive into function parameters: arguments vs parameters, positional vs keyword arguments, the mysteries of *args and **kwargs, unpacking secrets, and the sneaky pitfalls of default parameters. By the end, you’ll not only understand them, you’ll be able to explain them like an absolute genius.

Arguments vs Parameters, The First Misunderstanding

Let’s clear this up before we go too far:

  • Parameters are the names you define in the function.

  • Arguments are the actual values you pass when you call the function.

Think of it like this:

def make_pizza(size, topping):  # parameters
    print(f"Making a {size}-inch pizza with {topping}.")

make_pizza(12, "pepperoni")  # arguments

👉 Parameters = variables waiting for data. 👉 Arguments = the data you actually send.

Simple, but critical.

Positional vs Keyword Arguments

Python lets you call functions in two ways:

  1. Positional arguments order matters:

     make_pizza(16, "mushrooms")  # size=16, topping="mushrooms"
    
  2. Keyword arguments order doesn’t matter:

     make_pizza(topping="olives", size=14)
    

If you mix them, positional always goes first. Otherwise, Python gets confused:

make_pizza(12, topping="onions")  # ✅ fine
make_pizza(size=12, "onions")     # ❌ SyntaxError

The Magic of Unpacking

Unpacking is like giving Python a backpack of values and saying: “Here, open this and figure it out.”

Iterable unpacking with *:

def greet(a, b, c):
    print(a, b, c)

values = [1, 2, 3]
greet(*values)  # same as greet(1, 2, 3)

Dictionary unpacking with **:

def introduce(name, age):
    print(f"My name is {name}, I'm {age} years old.")

person = {"name": "Alice", "age": 25}
introduce(**person)  # same as introduce(name="Alice", age=25)

This is insanely powerful when you don’t know ahead of time how many values you’ll deal with.

Enter *args The Collector of Extras

Sometimes you don’t know how many arguments someone will pass. That’s where *args comes in.

def party(organizer, *guests):
    print(f"Organizer: {organizer}")
    print("Guests:", guests)

party("Alice", "Bob", "Charlie", "Dana")

Output:

Organizer: Alice
Guests: ('Bob', 'Charlie', 'Dana')

Notice that *args packs all extra positional arguments into a tuple. Think of it as: “whatever’s left over, put it in a bag.”

Enter **kwargs The Dictionary of Chaos

And then comes the sibling: **kwargs. It gathers all extra keyword arguments into a dictionary.

def profile(name, **details):
    print(f"Name: {name}")
    for key, value in details.items():
        print(f"{key}: {value}")

profile("Alice", age=25, city="London", hobby="chess")

Output:

Name: Alice
age: 25
city: London
hobby: chess

Boom! Suddenly, your function is infinitely flexible.

Combining *args and **kwargs

The ultimate weapon:

def everything(required, *args, **kwargs):
    print("Required:", required)
    print("Args:", args)
    print("Kwargs:", kwargs)

everything("Hello", 1, 2, 3, a=10, b=20)

Output:

Required: Hello
Args: (1, 2, 3)
Kwargs: {'a': 10, 'b': 20}

Golden rule of parameter order:

  1. Normal parameters

  2. *args

  3. Keyword-only parameters

  4. **kwargs

If you mess this up, Python will shout at you.

Extended Unpacking, Next-Level Wizardry

Python even lets you “spread” collections inside assignments:

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

This is brilliant for when you need the “edges” of a list but don’t care about the middle (or vice versa).

Parameter Defaults, Beware of Mutables!

Here’s a classic Python “gotcha”:

def add_item(item, bucket=[]):  # 🚨 Danger!
    bucket.append(item)
    return bucket

print(add_item(1))  # [1]
print(add_item(2))  # [1, 2]  <-- surprise!

Why? Because the default list is created once, not each time. So it keeps growing. The safe way:

def add_item(item, bucket=None):
    if bucket is None:
        bucket = []
    bucket.append(item)
    return bucket

Always be careful with mutable default arguments (lists, dicts, sets).

Putting It All Together

You now have the full toolkit:

  • Parameters vs arguments ✅

  • Positional & keyword arguments ✅

  • Unpacking iterables and dictionaries ✅

  • *args and **kwargs mastery ✅

  • Extended unpacking ✅

  • Default parameter pitfalls ✅

With these in your arsenal, you’re no longer just “using functions” you’re wielding them like a sorcerer.

The next time someone complains about messy function calls, you can smile, sip your coffee, and whisper: “Have you tried *args and **kwargs?”

0
Subscribe to my newsletter

Read articles from Anik Sikder directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anik Sikder
Anik Sikder

Full-Stack Developer & Tech Writer specializing in Python (Django, FastAPI, Flask) and JavaScript (React, Next.js, Node.js). I build fast, scalable web apps and share practical insights on backend architecture, frontend performance, APIs, and Web3 integration. Available for freelance and remote roles.