A Python lambda is a tiny anonymous function you write in one line, perfect for sorting, filtering, and callbacks. This guide shows you when a lambda beats a regular function, with examples that were all run on Python 3.14.6.
“In the face of ambiguity, refuse the temptation to guess.”
Tim Peters, The Zen of Python (PEP 20)
Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Beginner | Reading Time: 16 minutes
Say you have three coworkers, Viraj, Rahul, and Niranjan, and you want to sort their full names by last name. The sorted() function needs a key function, a small helper that pulls out the value to sort on. You could write a whole named function just for that. Here is the long way:
📄 verbose_sort.py (the long way)
names = ["Viraj Patil", "Rahul Mahadik", "Niranjan Raut"]
def get_last_name(full_name):
return full_name.split()[-1]
sorted_names = sorted(names, key=get_last_name)
print(sorted_names)
▶ Output
['Rahul Mahadik', 'Viraj Patil', 'Niranjan Raut']
That works. But get_last_name is three lines you will never use again. It exists only to serve that one sorted() call. A lambda gives you a cleaner way to do the same thing:
📄 lambda_sort.py (one line, same result)
names = ["Viraj Patil", "Rahul Mahadik", "Niranjan Raut"] sorted_names = sorted(names, key=lambda name: name.split()[-1]) print(sorted_names)
▶ Output
['Rahul Mahadik', 'Viraj Patil', 'Niranjan Raut']
Same result, and the throwaway function no longer clutters your file. That is the whole point of lambda. Think of it like a sticky note versus a printed sign: you scribble a lambda on the spot, use it once, and toss it. A regular def is the printed sign you hang up because you will need it again and again.
Table of Contents
Lambda Syntax
A lambda is a function with no name. Think of it like giving quick directions to an auto driver: no map, no written route, just “left, then second right.” All instruction, zero paperwork. The syntax is short enough to memorize in one look:
📄 lambda_basics.py (lambda anatomy)
# Regular function
def double(x):
return x * 2
# Same thing as a lambda
double_lambda = lambda x: x * 2
print(double(5)) # 10
print(double_lambda(5)) # 10
# Lambda with multiple parameters
add = lambda a, b: a + b
print(add(3, 7)) # 10
# Lambda with no parameters
greet = lambda: "Hello, TechnoScripts!"
print(greet())
▶ Output
10 10 10 Hello, TechnoScripts!
What happened here: lambda x: x * 2 builds a function that takes x and returns x * 2. The part before the colon is the parameter list. The part after the colon is a single expression, and its value is returned for you. There is no return keyword, because a lambda always returns its one expression. You can have zero, one, or many parameters, but only that single expression.
Lambda vs def: What Is the Difference?
The diagram puts the Python lambda and def side by side. A lambda is stuck with a single expression, while def gives you multiple statements, docstrings, and any logic you want. The takeaway is simple. Lambdas are a shortcut for tiny throwaway functions, so they shine as arguments to sorted(), map(), and filter(). Anything more involved deserves a proper def. The “when to use each” boxes at the bottom map directly to the code examples below.
📄 lambda_vs_def.py (the key differences)
# def: has a name, can hold multiple statements
def classify_age(age):
if age < 18:
return "minor"
elif age < 30:
return "young adult"
else:
return "adult"
# lambda: anonymous, single expression ONLY
classify_simple = lambda age: "minor" if age < 18 else "adult"
print(classify_age(25)) # young adult
print(classify_simple(25)) # adult (can't do elif in lambda)
# Check the names
print(classify_age.__name__) # classify_age
print(classify_simple.__name__) # <lambda>
▶ Output
young adult adult classify_age <lambda>
What happened here: def builds a named function that can hold multiple statements, loops, and exception handling. Anything goes. lambda builds an anonymous function limited to one expression, so it cannot do assignments, multiple lines, or branching beyond a single if/else expression. Notice the names too: the def reports its real name classify_age, while the lambda just reports <lambda>. If your function needs more than one expression, reach for def.
Sorting with Lambda
This is where lambda earns its keep. Sorting tuples, dictionaries, and other structured data needs a key function, and a lambda makes writing that key painless. A key function is like telling a librarian which part of each book to shelve by: author, title, or year, your call. In the examples below, the students list holds exam scores for four classmates, Rahul, Pravin, Vinay, and Aditi, and the team list holds three colleagues with their ages.
📄 sorting_lambdas.py (sort anything with lambda keys)
# Sort a list of tuples by second element (score)
students = [("Rahul", 88), ("Pravin", 95), ("Vinay", 72), ("Aditi", 91)]
by_score = sorted(students, key=lambda s: s[1], reverse=True)
print("By score (desc):", by_score)
# Sort dictionaries by a specific key
team = [
{"name": "Anvay", "age": 28},
{"name": "Niranjan", "age": 24},
{"name": "Viraj", "age": 31},
]
by_age = sorted(team, key=lambda person: person["age"])
print("By age:", [f'{p["name"]}({p["age"]})' for p in by_age])
# Sort strings by length, then alphabetically
words = ["python", "go", "rust", "java", "c"]
by_length = sorted(words, key=lambda w: (len(w), w))
print("By length+alpha:", by_length)
▶ Output
By score (desc): [('Pravin', 95), ('Aditi', 91), ('Rahul', 88), ('Vinay', 72)]
By age: ['Niranjan(24)', 'Anvay(28)', 'Viraj(31)']
By length+alpha: ['c', 'go', 'java', 'rust', 'python']
What happened here: The key parameter tells sorted() how to pull the comparison value out of each element. lambda s: s[1] grabs the score from each tuple. lambda person: person["age"] grabs the age from each dict. The tuple trick (len(w), w) sorts by length first, then alphabetically when two words are the same length. One detail to notice in the “By age” line: those are formatted strings (we wrapped each person in an f-string), so Python prints them with quotes, like 'Niranjan(24)'. That is just how a list of strings looks when you print it.
Lambda with Built-in Functions
Built-ins like min(), max(), and filter() accept a helper function, and lambda slots in perfectly. It works like asking a shopkeeper “give me the cheapest one”: you state the rule once, and the built-in does the walking through the shelves for you.
📄 lambda_builtins.py (lambda with min, max, filter)
products = [
{"name": "Laptop", "price": 85000},
{"name": "Mouse", "price": 1500},
{"name": "Keyboard", "price": 3500},
{"name": "Monitor", "price": 22000},
]
# Find cheapest and most expensive
cheapest = min(products, key=lambda p: p["price"])
priciest = max(products, key=lambda p: p["price"])
print(f"Cheapest: {cheapest['name']} (₹{cheapest['price']})")
print(f"Priciest: {priciest['name']} (₹{priciest['price']})")
# Filter products under ₹10,000
affordable = list(filter(lambda p: p["price"] < 10000, products))
print(f"Under ₹10K: {[p['name'] for p in affordable]}")
▶ Output
Cheapest: Mouse (₹1500) Priciest: Laptop (₹85000) Under ₹10K: ['Mouse', 'Keyboard']
What happened here: min(), max(), filter(), and sorted() all accept a key or function argument. A lambda is the perfect fit, because you only need a tiny function for that one call and nowhere else. min and max use the key to decide which product wins, and filter keeps only the products where the lambda returns True.
Lambda in Data Structures
📄 lambda_dispatch.py (lambdas in a dictionary as a dispatch table)
# Calculator using a dispatch dictionary
operations = {
"+": lambda a, b: a + b,
"-": lambda a, b: a - b,
"*": lambda a, b: a * b,
"/": lambda a, b: a / b if b != 0 else "Error: division by zero",
}
# Use it
for op in ["+", "-", "*", "/"]:
result = operations[op](10, 3)
print(f"10 {op} 3 = {result}")
print()
# Default values with lambda
from collections import defaultdict
word_counts = defaultdict(lambda: 0)
for word in "python is great python is fun".split():
word_counts[word] += 1
print(dict(word_counts))
▶ Output
10 + 3 = 13
10 - 3 = 7
10 * 3 = 30
10 / 3 = 3.3333333333333335
{'python': 2, 'is': 2, 'great': 1, 'fun': 1}
What happened here: The dispatch dictionary maps each operator string to a small lambda. Instead of a long chain of if/elif statements, you look up the operator and call whatever function you find. Think of it like a vending machine: press a button, get the matching item, no manual sorting required. The defaultdict(lambda: 0) uses a lambda to hand back a fresh 0 the first time a word shows up, so the counting just works.
Immediately Invoked Lambda
📄 iife_lambda.py (call a lambda right where you define it)
# Immediately invoked: rarely useful, but good to recognize
result = (lambda x, y: x ** y)(2, 10)
print(f"2^10 = {result}")
# More practical: inline conditional assignment
status = (lambda s: "pass" if s >= 40 else "fail")(35)
print(f"Score 35: {status}")
▶ Output
2^10 = 1024 Score 35: fail
You can wrap a lambda in parentheses and call it on the spot, like using a disposable cup: fill it, drink, toss it, all in one motion. This is rarely needed in Python, unlike JavaScript where immediately invoked function expressions show up everywhere. You will still bump into it in some codebases, so it is worth recognizing when you do.
When NOT to Use Lambda
A Python lambda is great for a sort key and terrible for tangled business logic. Here is a quick rule of thumb: if your lambda needs a comment to explain what it does, it has outgrown the butter knife and you should reach for a regular function.
📄 lambda_overuse.py (when a lambda becomes unreadable)
# BAD: this lambda is too complex to read
process = lambda data: {k: v for k, v in sorted(
((k, sum(v) / len(v)) for k, v in data.items()),
key=lambda x: x[1], reverse=True
)}
# GOOD: same logic, readable
def get_sorted_averages(data):
"""Sort categories by average score, highest first."""
averages = {}
for category, scores in data.items():
averages[category] = sum(scores) / len(scores)
return dict(sorted(averages.items(), key=lambda x: x[1], reverse=True))
scores = {"math": [85, 90, 78], "science": [92, 88, 95], "english": [70, 65, 80]}
print(get_sorted_averages(scores))
▶ Output
{'science': 91.66666666666667, 'math': 84.33333333333333, 'english': 71.66666666666667}
What happened here: Both versions produce the same result, but the lambda version reads like a puzzle and the def version reads like a story. Your future self, and your teammates, will thank you for choosing the readable one.
📄 pep8_violation.py (never assign a lambda to a variable, PEP 8)
# BAD: PEP 8 E731 says do not assign a lambda expression, use a def
square = lambda x: x ** 2
# GOOD: if you need a name, use def
def square(x):
return x ** 2
# Lambda is for ANONYMOUS use, inside sorted(), map(), filter()
# If you're giving it a name, just use def
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers)) # Fine, it stays anonymous
print(squared)
▶ Output
[1, 4, 9, 16, 25]
What happened here: Both square functions do the exact same thing, so why does PEP 8 (Python Enhancement Proposal 8, the official style guide) frown on the lambda version? Because once you give a function a name, def is the clearer choice. It shows a real name in tracebacks, it can carry a docstring, and tools recognize it instantly. The map(lambda x: x ** 2, numbers) call is fine, because there the lambda stays anonymous and is used right where it is born.
Real-World Examples
📄 real_world_lambdas.py (patterns you will see in production code)
# 1. Sorting API responses by response time
api_responses = [
{"endpoint": "/users", "time_ms": 245},
{"endpoint": "/products", "time_ms": 89},
{"endpoint": "/orders", "time_ms": 512},
]
slowest_first = sorted(api_responses, key=lambda r: r["time_ms"], reverse=True)
print("Slowest endpoints:")
for r in slowest_first:
print(f" {r['endpoint']}: {r['time_ms']}ms")
print()
# 2. Conditional formatting
scores = [92, 45, 78, 33, 88, 51]
results = list(map(lambda s: f"{s} (PASS)" if s >= 50 else f"{s} (FAIL)", scores))
print("Results:", results)
print()
# 3. Key extraction for grouping
from itertools import groupby
data = ["apple", "avocado", "banana", "blueberry", "cherry", "coconut"]
for letter, group in groupby(sorted(data), key=lambda w: w[0]):
print(f" {letter.upper()}: {list(group)}")
▶ Output
Slowest endpoints: /orders: 512ms /users: 245ms /products: 89ms Results: ['92 (PASS)', '45 (FAIL)', '78 (PASS)', '33 (FAIL)', '88 (PASS)', '51 (PASS)'] A: ['apple', 'avocado'] B: ['banana', 'blueberry'] C: ['cherry', 'coconut']
What happened here: Three everyday tasks, three one-line lambdas. The first sorts API (Application Programming Interface) endpoints from slowest to fastest, the kind of thing you do when hunting down a performance problem. The second tags each score as PASS or FAIL using an inline if/else expression. The third groups words by first letter with groupby, which needs a key function and gets a tiny lambda. One quick catch with groupby: it only groups items that are next to each other, so you must sort first, which is why we call sorted(data) before grouping.
Common Mistakes
Mistake 1: Trying to put statements in a lambda
📄 mistake_statements.py
# BAD: a lambda can't hold statements (assignment is a statement) # This line would be a SyntaxError: # action = lambda x: x = x + 1 # GOOD: use an expression instead transform = lambda x: x + 1 print(transform(5)) # 6
▶ Output
6
Mistake 2: Lambda in a loop with late binding
📄 mistake_late_binding.py (the classic closure trap)
# BAD: all lambdas share the same variable i
funcs_bad = [lambda: i for i in range(4)]
print("Bad:", [f() for f in funcs_bad]) # All return 3!
# GOOD: capture i's current value with a default argument
funcs_good = [lambda i=i: i for i in range(4)]
print("Good:", [f() for f in funcs_good]) # Returns 0, 1, 2, 3
▶ Output
Bad: [3, 3, 3, 3] Good: [0, 1, 2, 3]
Here is the trap. A lambda in a loop captures the variable i, not the number that was in it at the time. By the moment you actually call the lambda, the loop has finished and i is sitting at its final value, so every lambda reports the same number. The fix is the i=i default argument, which snapshots the current value at each step of the loop. Think of it like writing down today’s date on a sticky note instead of pointing at a wall calendar that keeps changing.
Mistake 3: Using lambda when a list comprehension is cleaner
📄 mistake_comprehension.py
numbers = [1, 2, 3, 4, 5, 6, 7, 8] # Meh: filter + lambda evens_lambda = list(filter(lambda x: x % 2 == 0, numbers)) # Better: list comprehension (more Pythonic) evens_comp = [x for x in numbers if x % 2 == 0] print(evens_lambda) # [2, 4, 6, 8] print(evens_comp) # [2, 4, 6, 8]
▶ Output
[2, 4, 6, 8] [2, 4, 6, 8]
In Python, list comprehensions are almost always the better pick over map() or filter() with a lambda. They read more clearly and often run a touch faster. So here is the split to remember: use a lambda for sorted() keys and callbacks, and use a comprehension when you are transforming or filtering a collection.
Best Practices
- DO use lambda for
sorted(),min(),max()key functions - DO use lambda for simple callbacks and dispatch tables
- DO keep lambdas to one simple expression
- DON’T assign lambdas to variables, use
definstead (PEP 8 E731) - DON’T write lambdas that need comments to understand
- DON’T use
map(lambda ...)when a list comprehension works
Practice Exercises
- Exercise 1: Write a lambda multiplying two numbers.
- Exercise 2: Sort (name, age) tuples by age with a lambda.
- Exercise 3: Use map, filter, reduce with lambdas to process prices.
Conclusion
Python lambdas are anonymous, single-expression functions built for short-lived use. They shine as sort keys, callback arguments, and dispatch values. The rule stays simple: if your lambda needs a name or a comment, make it a def. If it is a tiny throwaway function living inside a sorted() or filter() call, a lambda keeps your code clean and your intent obvious.
Next up: Recursion. Functions that call themselves, and why that is not as scary as it sounds. And if you want to revisit earlier topics or jump ahead, the full Python + AI/ML tutorial series home lists every post in order.
Frequently Asked Questions
What is a lambda function in Python?
A Python lambda is an anonymous (unnamed) function defined with the lambda keyword. It can take any number of parameters but contains only a single expression. Syntax: lambda parameters: expression. The expression is automatically returned.
When should I use lambda instead of def?
Use lambda when you need a small throwaway function as an argument to sorted(), min(), max(), filter(), or map(). If the function needs a name, multiple statements, or a docstring, use def instead.
Can a lambda have multiple statements?
No. A lambda is limited to a single expression. It cannot contain assignments, loops, or multiple statements. If you need multiple statements, use a regular def function.
Why does PEP 8 say not to assign lambdas to variables?
PEP 8 rule E731 says: do not assign a lambda expression, use a def. The reason: if you’re giving the function a name, def is clearer, provides a proper __name__ attribute, and allows a docstring. Lambda is designed for anonymous, inline use.
Is lambda faster than a regular function?
No. A Python lambda creates the same kind of function object as def, so there is no performance difference. The choice is purely about readability and convenience, not speed.
Can lambda functions have default parameter values?
Yes. lambda x, y=10: x + y works just like a def with default parameters. This is also the trick to avoid the late-binding closure problem in loops: lambda i=i: i.
Interview Questions on Python Lambda
How interviewers actually probe this topic: real scenarios, with answers you can say out loud.
Q: A lambda has no return keyword. How does it return a value?
A lambda body is a single expression, and Python automatically returns whatever that expression evaluates to. So lambda x: x * 2 returns x * 2 without any return keyword. In fact, writing return inside a lambda is a SyntaxError, because return is a statement and lambdas cannot contain statements.
Q: You created button callbacks in a loop with lambda: print(i), and every button prints the last index. What went wrong and how do you fix it?
This is the late-binding closure trap. Each lambda captures the variable i itself, not the value it held when the lambda was created, so by the time any callback runs, the loop has finished and i holds its final value. The standard fix is a default argument, lambda i=i: print(i), which snapshots the current value at definition time. Alternatively, use functools.partial(print, i) for the same effect.
Q: A production traceback ends with a line that just says in <lambda> and you cannot tell which of several lambdas crashed. What does this tell you about lambdas, and how would you make debugging easier?
Every lambda reports its __name__ as <lambda>, so tracebacks cannot distinguish one lambda from another. The traceback still shows the file and line number, which helps, but the fix is structural: convert any lambda complex enough to fail into a small named def. Then the traceback shows a real function name, and you can also attach a docstring and unit tests to it.
Q: How would you sort a list of product dicts by price ascending, and by name alphabetically when prices tie?
Return a tuple from the key function: sorted(products, key=lambda p: (p["price"], p["name"])). Python compares tuples element by element, so it sorts by price first and only looks at the name when two prices are equal. This tuple-key trick handles almost any multi-level sort without extra code.
Q: Is there anything a lambda can do that def cannot?
No. Anything a lambda does, a def can do too, since both create the same kind of function object. The only difference is convenience: a lambda is an expression, so you can drop it inline exactly where a function argument is expected, without naming it first. The reverse is not true, because def supports statements, docstrings, decorators, and annotations that lambdas cannot have.
Q: When would you pick a list comprehension over map() or filter() with a lambda?
Almost always, when transforming or filtering a collection. [x * 2 for x in nums if x > 0] reads left to right and avoids the extra lambda call overhead per element. map() and filter() still make sense when you already have a named function to pass, like map(str.strip, lines), or when you want a lazy iterator instead of building a full list in memory.
Go deeper: when you outgrow this post, the official Python documentation is the next stop.
Related Posts
Previous: Python: Scope and the LEGB Rule (Local, Enclosing, Global)
Next: Python: Recursion, Base Cases, Stack, Practical Examples
Series Home: Python + AI/ML Tutorial Series

No comment