Lesson 13: Lambda, *args & **kwargs
Anonymous one-line functions and flexible functions that accept any number of arguments.
Lambda — Tiny Anonymous Functions
A lambda is a one-line function without a name:
double = lambda x: x * 2
print(double(5)) # 10
It is exactly equivalent to def double(x): return x * 2, but written as an expression. Lambdas shine where you need a quick, throwaway function — especially as a sort key or with map/filter.
What You'll Learn in This Lesson
- Write lambdas and use them as sort keys
- Accept any number of arguments with
*args - Accept any number of keyword arguments with
**kwargs - Unpack sequences and dicts with
*and**
Lambda as a Sort Key
The most common lambda use case — custom sorting:
products = [
{"name": "Laptop", "price": 800},
{"name": "Mouse", "price": 25},
{"name": "Monitor", "price": 300},
]
products.sort(key=lambda p: p["price"])
# Sorts by price — ['Mouse', 'Monitor', 'Laptop']
The lambda receives each item and returns the value to sort by. You can also sort by length, last letter, or any computed property:
words = ["apple", "banana", "cherry"]
print(sorted(words, key=lambda w: w[-1])) # sort by last letter
Mental model: each item gets a "score tag" from the lambda, then items are ordered by their tags.
Other classic lambda uses:
# map: apply to every item
squares = list(map(lambda x: x ** 2, [1, 2, 3])) # [1, 4, 9]
# filter: keep items passing the test
evens = list(filter(lambda x: x % 2 == 0, range(10))) # [0, 2, 4, 6, 8]
# max with a key
longest = max(["python", "ai", "code"], key=len) # 'python'
In modern Python, list comprehensions often replace map/filter — but lambdas with key= in sort/max/min remain irreplaceable.
*args — Any Number of Positional Arguments
Putting *args in a function collects all extra positional arguments into a tuple:
def total(*args):
return sum(args)
print(total(1, 2, 3)) # 6
print(total(5, 10)) # 15
print(total()) # 0
The name args is a convention — the * is what matters. This pattern is perfect for sums, averages, and flexible APIs.
Mental model:
*argsis a funnel that pours any number of loose values into one bag (a tuple). The bag arrives inside the function ready to loop over.
**kwargs — Any Number of Keyword Arguments
**kwargs collects extra named arguments into a dictionary:
def profile(name, **kwargs):
print(f"Name: {name}")
for key, value in kwargs.items():
print(f" {key}: {value}")
profile("Amol", role="Trainer", city="Mumbai")
# Name: Amol
# role: Trainer
# city: Mumbai
Frameworks use this to pass arbitrary options to functions without exploding the signature.
Mental model:
**kwargsis a coat-check desk — every named item (name=value) is hung on a labeled hook in a dictionary.
Parameter Order — The Golden Rule
The order is fixed:
def f(normal, *args, keyword_only, **kwargs):
- Normal parameters
*args- Keyword-only parameters (after
*) **kwargs
Keyword-only parameters (after a bare *) can only be passed by name — they protect against accidental misordering:
def connect(host, *, port=443):
print(host, port)
connect("example.com") # example.com 443
connect("example.com", port=8080) # example.com 8080
# connect("example.com", 8080) # TypeError! port is keyword-only
Unpacking with * and **
The * and ** also work in reverse — unpacking collections when calling:
def add(a, b, c):
return a + b + c
nums = [1, 2, 3]
print(add(*nums)) # 6 — list unpacked into 3 args
config = {"a": 1, "b": 2, "c": 3}
print(add(**config)) # 6 — dict unpacked into keyword args
A real-world use — forwarding arguments. Wrappers and decorators (Lesson 14) capture everything with *args, **kwargs and forward it unchanged:
def logged_call(func):
def wrapper(*args, **kwargs):
print("Calling", func.__name__)
return func(*args, **kwargs) # forward everything
return wrapper
When Lambdas Work Best — and When Not
| ✅ Use a lambda | ❌ Use a def |
|---|---|
| Sort keys | More than one expression |
| Passing behavior to map/filter | Statements (if, loops) |
| Tiny one-off logic | Anything you'd reuse by name |
Keep lambdas to ONE expression. If it needs statements, write a def.
Common Mistakes to Avoid
- Mistake: Writing
lambda x: if x > 0: ...— Fix: lambdas cannot contain statements; use a def. - Mistake: Ordering parameters wrong (
def f(**kwargs, *args)) — Fix: follow the golden order. - Mistake: Capturing a loop variable in a lambda — Fix: give the lambda a default:
lambda x, i=i: .... - Mistake: Forgetting
*when callingadd(*nums)— Fix: without the star, you pass a single list as one argument. - Mistake: Naming the parameters
args/kwargsand thinking the names matter — Fix: the*/**syntax is what does the work.
Professional Tips & Tricks
- Use lambdas only for tiny logic; for anything complex, def a real function.
*argsis perfect for variadic sums, logs, and wrappers that must pass arguments through.**kwargslets you write configurable functions without exploding the signature.- Use bare
*to force keyword-only arguments and prevent misordering bugs. - In decorators and wrappers, always forward
*args, **kwargsunchanged.
Key Takeaways
lambda x: expris a nameless one-expression function.*args→ tuple of extra positional args;**kwargs→ dict of extra keyword args.- Parameter order: normal →
*args→ keyword-only →**kwargs. *listand**dictunpack when calling functions.- Lambdas shine as
key=functions for sort/max/min.
Next up: Scope, closures & decorators — the professional power tools.
# Lambda as a sort key
products = [
{"name": "Laptop", "price": 800},
{"name": "Mouse", "price": 25},
{"name": "Monitor", "price": 300},
]
products.sort(key=lambda p: p["price"])
print("Cheapest first:", [p["name"] for p in products])
# *args collects positional arguments into a tuple
def total(*args):
return sum(args)
print("total(1,2,3):", total(1, 2, 3))
print("total(5, 10):", total(5, 10))
# **kwargs collects keyword arguments into a dict
def profile(name, **kwargs):
print(f"Name: {name}")
for key, value in kwargs.items():
print(f" {key}: {value}")
profile("Amol", role="Trainer", city="Mumbai")Lesson Code (Python)
# Lambda as a sort key
products = [
{"name": "Laptop", "price": 800},
{"name": "Mouse", "price": 25},
{"name": "Monitor", "price": 300},
]
products.sort(key=lambda p: p["price"])
print("Cheapest first:", [p["name"] for p in products])
# *args collects positional arguments into a tuple
def total(*args):
return sum(args)
print("total(1,2,3):", total(1, 2, 3))
print("total(5, 10):", total(5, 10))
# **kwargs collects keyword arguments into a dict
def profile(name, **kwargs):
print(f"Name: {name}")
for key, value in kwargs.items():
print(f" {key}: {value}")
profile("Amol", role="Trainer", city="Mumbai")Console Output
Cheapest first: ['Mouse', 'Monitor', 'Laptop']
total(1,2,3): 6
total(5, 10): 15
Name: Amol
role: Trainer
city: MumbaiCode Visualization Tips
- Picture *args as a funnel collecting loose items into one bag (tuple).
- Picture **kwargs as a coat-check desk collecting named items into a labeled cabinet (dict).
- For sort keys, visualize each item getting a 'score tag' from the lambda, then being ordered by that tag.
Professional Tips & Tricks
- Use lambdas only for tiny logic; for anything complex, def a real function.
- *args is perfect for variadic sums, logs, and wrappers that must pass arguments through.
- **kwargs lets you write configurable functions without exploding the signature.
Python Code Judge & Practice Arena
LeetCode StyleRun real Python 3.12 WebAssembly code directly in your browser against automated test suites.
Problem 1: Variadic Aggregator 1
Test Your Knowledge
Instant feedbackQuick Check: Lesson 13: Lambda Functions, *args, **kwargs & Unpacking
Up next · Continue learning
Scope, Closures & Decorators
LEGB scoping rules, closures that remember, and decorators that wrap functions with extra behavior.