How do I Fix a Add Default Parameters to Functions When Using Type Hinting

When I first started combining default parameters with type hinting in Python, I ran into confusion. At first glance, it seemed simple but when mutable defaults like dictionaries came into play, I quickly realized things weren’t as straightforward as I thought.

Let me walk you through my journey step by step, the mistakes I made, and how I finally fixed it.

The First Attempt (Problematic Code)

I began with something simple:

def foo(name, opts={}):
    pass

At first glance, this looks fine. If no options are passed, opts defaults to an empty dictionary.

But here’s the catch:

  • This introduces a classic Python bug.
  • Since {} is mutable, the same dictionary persists across function calls.
  • That means if the dictionary is modified in one call, the changes leak into future calls.

This isn’t what I wanted at all.

Adding Type Hints the Wrong Way

My next step was to add type hints. I tried this:

def foo(name: str, opts={}: dict) -> str:
    pass

And boom syntax error.

The reason? Python expects the type before the default value, not after. So my placement was wrong.

The “Works but Misleading” Way

Next, I tried:

def foo(name: str, opts: dict = {}) -> str:
    pass

This worked syntactically, but it still suffered from the mutable default argument problem. Multiple calls would share the same dictionary instance. Not safe at all.

So while it looked okay, it wasn’t the right solution.

The Correct Way

Finally, I discovered the right approach: use None as the default, and create a new dictionary inside the function when needed.

from typing import Optional

def foo(name: str, opts: Optional[dict] = None) -> str:
    if opts is None:
        opts = {}
    return f"Hello {name}, with options: {opts}"

Why this works:

  • Type clarity: opts is either a dict or None.
  • Safe defaults: A new dictionary is created each time.
  • Readable code: Anyone reading this immediately understands the intention.

Adding More Practice Functionality

Once I had the foundation fixed, I decided to make the function more practical type hinting. What if I wanted to customize greetings using the dictionary?

Here’s my expanded version:

from typing import Optional, Dict

def foo(name: str, opts: Optional[Dict[str, str]] = None) -> str:
    if opts is None:
        opts = {}
    
    greeting = opts.get("greeting", "Hello")
    punctuation = opts.get("punctuation", "!")
    style = opts.get("style", "plain")

    message = f"{greeting}, {name}{punctuation}"

    if style == "upper":
        return message.upper()
    elif style == "lower":
        return message.lower()
    return message

Example Usage

print(foo("Alice"))  
# Output: Hello, Alice!

print(foo("Bob", {"greeting": "Hi"}))  
# Output: Hi, Bob!

print(foo("Charlie", {"style": "upper", "punctuation": "!!"}))  
# Output: HELLO, CHARLIE!!

With this approach, I can safely use type hints, avoid bugs, and add flexibility to my function.

Final Thought

In the end, fixing default parameters with type hinting in Python comes down to avoiding mutable defaults and using None wisely. By combining Optional type hints with safe initialization inside the function, I get cleaner, more reliable, and bug-free code. It’s a small adjustment that makes a big difference in writing professional Python functions.

Related blog posts