How to pass kwargs in Python

A complete guide to passing kwargs in Python. Learn different methods, get useful tips, see real-world examples, and debug common errors.

How to pass kwargs in Python
Published on: 
Tue
Mar 10, 2026
Updated on: 
Fri
Mar 13, 2026
The Replit Team

Python's **kwargs lets you pass a variable number of keyword arguments to a function. This feature makes your functions more flexible and is essential to build adaptable code.

Here, you'll learn techniques to use **kwargs effectively. You will also discover real-world applications and get practical advice to debug common issues you might encounter along the way.

Using **kwargs to accept arbitrary keyword arguments

def greet(**kwargs):
   for key, value in kwargs.items():
       print(f"{key}: {value}")

greet(name="Alice", age=30, city="New York")--OUTPUT--name: Alice
age: 30
city: New York

The greet function uses **kwargs to capture any number of keyword arguments. This is why the function call works with name="Alice", age=30, and city="New York" even though they aren't formal parameters. The double-asterisk syntax packs these arguments into a dictionary that the function can then access.

Inside the function, kwargs is treated just like a standard dictionary. You can use familiar methods like .items() to iterate through the key-value pairs you've passed in, giving you a simple way to work with flexible inputs.

Basic kwargs techniques

Building on the idea that **kwargs is a dictionary, you'll now see how to access its values, unpack other dictionaries, and combine it with *args.

Accessing values from the kwargs dictionary

def process_user(**kwargs):
   username = kwargs.get('username', 'Anonymous')
   role = kwargs.get('role', 'User')
   print(f"User {username} has role: {role}")

process_user(username="admin", role="Administrator")--OUTPUT--User admin has role: Administrator

The .get() method is a safe way to access values from the kwargs dictionary. It lets you look up a key and provide a default value if that key doesn't exist, which helps you avoid errors.

  • In the process_user function, kwargs.get('username', 'Anonymous') tries to find a 'username'. If it can't, the function uses 'Anonymous' instead.
  • This technique is great for handling optional arguments without causing a KeyError if they aren't provided in the function call.

Unpacking dictionaries as kwargs

def create_profile(name, email, **extra):
   print(f"Creating profile for {name} ({email})")
   for key, value in extra.items():
       print(f"  {key}: {value}")

user_data = {"location": "San Francisco", "interests": ["Python", "AI"]}
create_profile("Bob", "[email protected]", **user_data)--OUTPUT--Creating profile for Bob ([email protected])
 location: San Francisco
 interests: ['Python', 'AI']

You can also unpack a dictionary's contents directly into a function's keyword arguments using the double-asterisk ** operator. In the create_profile example, **user_data passes the key-value pairs from the user_data dictionary as if they were individual arguments in the function call.

  • This technique is incredibly useful when you have data already structured in a dictionary. The function's **extra parameter simply collects these arguments, allowing you to work with dynamic or pre-configured information without extra steps.

Combining positional arguments, *args, and **kwargs

def flexible_function(required, *args, **kwargs):
   print(f"Required parameter: {required}")
   print(f"Positional args: {args}")
   print(f"Keyword args: {kwargs}")

flexible_function("Hello", 1, 2, 3, name="Python", version=3.9)--OUTPUT--Required parameter: Hello
Positional args: (1, 2, 3)
Keyword args: {'name': 'Python', 'version': 3.9}

Python enforces a specific order when you combine these parameter types in a function definition: standard arguments first, then *args, and finally **kwargs. This structure is what gives the flexible_function its power.

  • The first argument, "Hello", is assigned to the required positional parameter, required.
  • *args then collects any remaining positional arguments—in this case, 1, 2, 3—into a tuple.
  • **kwargs gathers all keyword arguments, like name="Python" and version=3.9, into a dictionary.

Advanced kwargs techniques

Building on the basics, you'll now see how **kwargs enables more complex patterns, like forwarding arguments between functions and using them within class methods.

Forwarding kwargs to another function

def format_data(**kwargs):
   return {k.upper(): v for k, v in kwargs.items()}

def process_and_format(value, **kwargs):
   print(f"Processing value: {value}")
   formatted = format_data(**kwargs)
   print(f"Formatted data: {formatted}")

process_and_format("test", name="Alice", id=12345)--OUTPUT--Processing value: test
Formatted data: {'NAME': 'Alice', 'ID': 12345}

You can use **kwargs to pass arguments from one function to another, a technique known as forwarding. This pattern is perfect for creating flexible wrapper functions that don't need to know the specifics of the function they're calling.

  • The process_and_format function gathers keyword arguments like name="Alice" into its own kwargs dictionary.
  • It then unpacks this dictionary using **kwargs when calling format_data.
  • This sends the original arguments along, allowing format_data to process them independently without the wrapper function needing to know what they are.

Using kwargs with default parameters

def configure_app(debug=False, **settings):
   config = {"debug": debug, "version": "1.0"}
   config.update(settings)
   print(f"App configuration: {config}")

configure_app(debug=True, host="localhost", port=8080, timeout=30)--OUTPUT--App configuration: {'debug': True, 'version': '1.0', 'host': 'localhost', 'port': 8080, 'timeout': 30}

You can combine **kwargs with parameters that have default values, like debug=False in the configure_app function. This lets you define core settings while still accepting any number of extra options. It’s a powerful pattern for creating functions with a mix of fixed and flexible arguments.

  • The **settings parameter gathers all other keyword arguments, such as host and port, into a dictionary.
  • Using config.update(settings), you can then merge these optional settings into a base configuration, making it easy to add new configurations on the fly.

Using **kwargs in class methods and inheritance

class Parent:
   def __init__(self, **kwargs):
       self.config = kwargs
       print(f"Parent initialized with: {self.config}")

class Child(Parent):
   def __init__(self, name, **kwargs):
       super().__init__(**kwargs)  # Pass kwargs to parent
       self.name = name
       print(f"Child {name} initialized")

child = Child(name="Child1", setting1="value1", setting2="value2")--OUTPUT--Parent initialized with: {'setting1': 'value1', 'setting2': 'value2'}
Child Child1 initialized

In classes, **kwargs is powerful for managing initialization options across an inheritance hierarchy. The Child class’s __init__ method captures its specific name argument, while **kwargs collects any others, like setting1 and setting2.

  • The key is the super().__init__(**kwargs) call, which forwards the extra arguments to the Parent class’s initializer.
  • This pattern decouples the child from the parent. The Child class doesn't need to know what arguments its parent accepts, making the design more flexible and easier to maintain.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. You can describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

The **kwargs techniques from this article are perfect for building flexible applications. With Replit Agent, you can turn these concepts into production tools:

  • Build a customizable logging utility that uses **kwargs to capture optional metadata like user_id or session_token.
  • Create a flexible component rendering function that passes any number of HTML attributes or style properties using **kwargs.
  • Deploy an API client that forwards optional filter and sorting parameters to an endpoint by collecting them with **kwargs.

Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically, all in your browser.

Common errors and challenges

While **kwargs is powerful, a few common mistakes can trip you up, but they're easy to fix once you know what to look for.

  • Forgetting to unpack dictionaries with **. A frequent mistake is passing a dictionary to a function that expects **kwargs without unpacking it using the ** operator. If you call a function like my_func(my_dict) instead of my_func(**my_dict), Python sees the whole dictionary as a single positional argument, which usually results in a TypeError.
  • Keyword argument after **kwargs parameter. Python's function syntax has a strict order. If you try to define a function with a keyword argument after **kwargs, such as def my_func(**kwargs, another_arg='default'), you'll get a SyntaxError because the order is invalid.
  • Using non-string keys with **kwargs. Keyword argument names must always be strings. If you try to unpack a dictionary that contains non-string keys, like {1: 'one'}, into a function call with **, Python will raise a TypeError because integer keys aren't valid identifiers for keyword arguments.

Forgetting to unpack dictionaries with **

It's easy to forget the double-asterisk ** when passing a dictionary to a function expecting keyword arguments. Python then treats the dictionary as a single positional argument, not separate keywords, which typically causes an error. The following code shows this mistake in action.

def create_user(username, email, **profile_data):
   print(f"User: {username}, Email: {email}")
   print(f"Profile data: {profile_data}")

user_profile = {"bio": "Python developer", "location": "Remote"}
create_user("jane_doe", "[email protected]", user_profile)  # Wrong!

The create_user function is called with three positional arguments, but it only accepts two. The user_profile dictionary is incorrectly passed as a third argument, causing an error. The following example shows the correct way to call it.

def create_user(username, email, **profile_data):
   print(f"User: {username}, Email: {email}")
   print(f"Profile data: {profile_data}")

user_profile = {"bio": "Python developer", "location": "Remote"}
create_user("jane_doe", "[email protected]", **user_profile)  # Correctly unpacks

The fix is to unpack the dictionary using the double-asterisk operator (**). When you call create_user(**user_profile), you're telling Python to treat the dictionary's contents as individual keyword arguments. This expands user_profile, passing bio="Python developer" and location="Remote" to the function.

The **profile_data parameter then correctly collects these arguments. You'll often encounter this pattern when passing configuration data or API responses that are already stored in a dictionary.

Keyword argument after **kwargs parameter

Python's function definitions follow a strict order, and placing a keyword argument after the **kwargs parameter violates this rule. This mistake will immediately raise a SyntaxError because the interpreter can't parse the invalid signature. The following code shows this error in practice.

def configure_app(**settings, debug=False):  # SyntaxError
   config = {"debug": debug}
   config.update(settings)
   print(f"Configuration: {config}")

configure_app(host="localhost", port=8080, debug=True)

The configure_app function defines debug=False after **settings, which isn't valid syntax. Python expects keyword-only arguments to appear before **kwargs in a function signature. The following example shows the correct way to structure the function.

def configure_app(debug=False, **settings):  # Named params before **kwargs
   config = {"debug": debug}
   config.update(settings)
   print(f"Configuration: {config}")

configure_app(debug=True, host="localhost", port=8080)

The fix is to define named parameters before **kwargs. By placing debug=False before **settings in the configure_app function, you follow Python's required order. This lets the function correctly assign the debug argument, while **settings collects all other keyword arguments like host and port. Keep this rule in mind when creating functions that mix explicit and arbitrary keyword arguments.

Using non-string keys with **kwargs

Keyword arguments in Python must be strings. When you unpack a dictionary using the ** operator, its keys become these arguments. If your dictionary contains non-string keys, like an integer, Python can't process it and raises a TypeError.

The following code shows what happens when you try to unpack a dictionary containing a non-string key into a function that accepts **kwargs.

data = {
   "name": "Product",
   "price": 29.99,
   123: "identifier"  # Non-string key
}

def process_product(**product_data):
   for key, value in product_data.items():
       print(f"{key}: {value}")

process_product(**data)  # TypeError: keywords must be strings

The call to process_product(**data) fails because the dictionary key 123 is an integer. Python cannot convert this number into a valid keyword argument, as all keyword arguments must be strings. The following example shows how to fix this.

data = {
   "name": "Product",
   "price": 29.99,
   "id": 123  # String key with numeric value
}

def process_product(**product_data):
   for key, value in product_data.items():
       print(f"{key}: {value}")

process_product(**data)  # Works correctly

The fix is to ensure all dictionary keys are strings before unpacking them. The ** operator requires string keys to create valid keyword arguments for a function. In the corrected example, the integer key 123 is replaced with the string "id", allowing the process_product(**data) call to work as expected. This issue often arises when you're processing data from external sources like APIs, where keys might not always be strings.

Real-world applications

Now that you can navigate the common pitfalls, you'll see how **kwargs is used to build flexible database queries and API wrappers.

Using **kwargs to build database queries

**kwargs is perfect for building functions that generate dynamic database queries, allowing you to filter results based on any number of search criteria.

def find_users(**query_params):
   if not query_params:
       return "SELECT * FROM users"
   
   base_query = "SELECT * FROM users WHERE "
   conditions = [f"{key} = '{value}'" for key, value in query_params.items()]
   final_query = base_query + " AND ".join(conditions)
   
   return final_query

# Generate different queries based on provided parameters
print(find_users())
print(find_users(username="john_doe"))
print(find_users(status="active", role="admin"))

The find_users function uses **query_params to dynamically build SQL queries based on the arguments you provide. This makes your code adaptable to various search needs.

  • When called without arguments, it generates a simple query to fetch all users.
  • When you pass keyword arguments like status="active", it uses them to construct a WHERE clause, linking multiple conditions with AND.

This approach is highly efficient. You can filter by any combination of fields without needing to write custom logic for each query, making your data retrieval functions much more reusable.

Creating flexible API wrappers with **kwargs

You can also use **kwargs to build flexible API clients that merge default settings with custom parameters for each request.

class APIClient:
   def __init__(self, base_url, **default_params):
       self.base_url = base_url
       self.default_params = default_params
       
   def get_resource(self, endpoint, **request_params):
       # Combine default params with request-specific params
       params = {**self.default_params, **request_params}
       
       query_string = "&".join([f"{k}={v}" for k, v in params.items()])
       url = f"{self.base_url}/{endpoint}"
       if query_string:
           url += f"?{query_string}"
           
       return f"GET {url}"

client = APIClient("https://api.example.com", version="v1", api_key="abc123")
print(client.get_resource("users"))
print(client.get_resource("posts", limit=10, sort="date"))

The APIClient class uses **kwargs to manage different layers of configuration. The __init__ method uses **default_params to store reusable settings like version and api_key. When you call get_resource, it uses **request_params for call-specific options.

  • The line params = {**self.default_params, **request_params} is key. It unpacks both dictionaries into a new one, with values from request_params overwriting any defaults if their keys match.
  • This pattern lets you create a pre-configured client while still allowing custom overrides for individual API calls, like adding limit=10.

Get started with Replit

Turn your knowledge of **kwargs into a real tool. Tell Replit Agent: "Build a Python function that creates a JSON config file from keyword arguments" or "Create a script that filters a list of dictionaries using keyword arguments."

Replit Agent writes the code, tests for errors, and deploys your app. Start building with Replit.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started for free

Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.