# Python Lambda Function: Where It Helps and When to Use def

> A Python lambda function is a one-expression anonymous function. Learn where it helps (sort keys, callbacks), when def wins, and the loop bug it causes.

- **Author:** Ayushi Kulshreshta — AI Engineer (https://www.1stepgrow.com/authors/ayushi-kulshreshta/)
- **Published:** Jul 18, 2026 · **Updated:** Sep 17, 2026
- **Topic:** Python & Programming · **Format:** Guide · **Read time:** 9 min
- **Canonical URL:** https://www.1stepgrow.com/articles/python-lambda-functions/

## Key takeaways

- A lambda is a single expression with an implicit return — no statements, no annotations, no docstring.
- The best use is as a throwaway key function for sorted, min, max and groupby.
- Assigning a lambda to a name defeats the point; use def, which also gives you a real name in tracebacks.
- The late-binding closure gotcha in loops catches everyone — bind with a default argument or functools.partial.
- For plain item or attribute access, operator.itemgetter and attrgetter are clearer than a lambda.

A Python lambda function is a small anonymous function written as a single expression: `lambda x: x ** 2`. It returns the value of that expression automatically. Use it where a short function is passed straight to another function, such as a sort key, and use `def` everywhere else.

Lambdas look simple, which is how they cause trouble. Three lambdas built in a loop can all return `12` when you expected `10`, `11` and `12`, and a lambda over a large DataFrame can be far slower than one vectorised line. This guide is for Python learners who already write functions with `def` and want to know when the short form is worth it. It covers the best uses, when `def` is clearer, the loop gotcha, and the tools that often replace a lambda.

Lambdas are the last stop in the functions series; if `def`, arguments and scope are still new, start with [user-defined Python functions](https://www.1stepgrow.com/articles/python-functions).

## What is a lambda in Python?

A lambda is a function without a name, limited to a single expression. The official tutorial's [lambda expressions section](https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions) describes them as small anonymous functions:

```python
square = lambda x: x ** 2       # works, but see below
square(4)                        # 16

# equivalent
def square(x):
    return x ** 2
```

The `return` is implicit. As a result, there is no room for statements, loops, assignments, annotations or a docstring. Writing `lambda x: return x` is a `SyntaxError`.

That constraint is the whole design. Lambdas exist for the case where naming the function would be more noise than signal.

## Lambda vs def at a glance

| | `lambda` | `def` |
|---|---|---|
| Kind | expression | statement |
| Body | one expression | any number of statements |
| Return | implicit | explicit `return` |
| Name in tracebacks | `<lambda>` | the real function name |
| Docstring and type hints | no | yes |
| Best for | short callbacks and key functions | everything else |

## Where does a Python lambda function actually help?

**Sort keys** are the single best use:

```python
people = [("Ananya", 28), ("Rohit", 35), ("Meera", 24)]

sorted(people, key=lambda p: p[1])              # by age
# [('Meera', 24), ('Ananya', 28), ('Rohit', 35)]
sorted(people, key=lambda p: p[1], reverse=True)
# [('Rohit', 35), ('Ananya', 28), ('Meera', 24)]
sorted(people, key=lambda p: (p[1], p[0]))      # age, then name
```

That last one — a tuple key for multi-level sorting — is a pattern worth remembering. The [sorting HOWTO](https://docs.python.org/3/howto/sorting.html#key-functions) notes that the key function is called exactly once per item, so even a lambda key stays fast.

Sorting dictionaries and objects works the same way:

```python
scores = {"Ananya": 91, "Rohit": 78, "Meera": 85}
sorted(scores.items(), key=lambda kv: kv[1], reverse=True)
# [('Ananya', 91), ('Meera', 85), ('Rohit', 78)]

from collections import namedtuple
Employee = namedtuple("Employee", "name salary")
employees = [Employee("Ananya", 95000), Employee("Rohit", 72000)]
sorted(employees, key=lambda e: e.salary)
# [Employee(name='Rohit', salary=72000), Employee(name='Ananya', salary=95000)]
```

However, you do not always need a lambda. When a named function already does the job, pass it directly:

```python
words = ["kiwi", "Banana", "fig", "apple"]
sorted(words)                   # ['Banana', 'apple', 'fig', 'kiwi'] - uppercase first
sorted(words, key=str.lower)    # ['apple', 'Banana', 'fig', 'kiwi']
max(scores, key=scores.get)     # 'Ananya' - the key with the highest value
```

**min, max and aggregates:**

```python
records = [{"name": "a", "score": 90}, {"name": "b", "score": 75}]
max(records, key=lambda r: r["score"])     # {'name': 'a', 'score': 90} - the whole record
min(words, key=len)                         # 'fig'
```

Returning the item rather than the value is why `key=` beats a comprehension here.

**pandas apply, for small transformations:**

```python
df["initial"] = df["name"].apply(lambda s: s[0].upper())
df["band"] = df["score"].apply(lambda x: "high" if x > 80 else "low")
```

Note the performance caveat below, though.

**Callbacks and default factories:**

```python
from collections import defaultdict
counts = defaultdict(lambda: 0)
nested = defaultdict(lambda: defaultdict(list))
nested["north"]["q1"].append(1200)
```

`defaultdict(lambda: defaultdict(list))` is an idiomatic use, because you need a callable and naming it would not help.

## When should you use def instead?

**Never assign a lambda to a name.** [PEP 8's programming recommendations](https://peps.python.org/pep-0008/#programming-recommendations) say to always use `def` instead, and the reason is practical:

```python
# don't
calculate_tax = lambda amount, rate: amount * rate

# do
def calculate_tax(amount, rate):
    return amount * rate
```

The `def` version has a real `__name__`, so tracebacks say `calculate_tax` rather than `<lambda>`. In a stack trace full of `<lambda>` entries, that matters.

**Use def when the logic needs explaining:**

```python
# unreadable
process = lambda r: (r["a"] * 2 + r["b"]) / (r["c"] if r["c"] else 1)

# clear
def normalised_score(record):
    """Combine a and b, normalised by c (guarding division by zero)."""
    combined = record["a"] * 2 + record["b"]
    divisor = record["c"] or 1
    return combined / divisor

normalised_score({"a": 3, "b": 4, "c": 0})    # 10.0
```

**Use def when you need more than an expression.** There is no `try`, no loop and no assignment statement inside a lambda. So if you are reaching for a workaround, use `def`.

## Why do lambdas created in a loop share one value?

Because a closure looks up the loop variable when it runs, not when it is created. This catches everyone once:

```python
funcs = []
for i in range(3):
    funcs.append(lambda x: x + i)

[f(10) for f in funcs]      # [12, 12, 12]  - not [10, 11, 12]
```

Closures look up the *variable* when they run, not its value when they were created. By the time the lambdas run, `i` is 2. The Python FAQ covers this exact case: [why lambdas defined in a loop all return the same result](https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result). The same thing happens with `def` inside a loop, so it is not a lambda-only quirk.

Bind the value explicitly with a default argument, which is evaluated at definition time:

```python
funcs = [lambda x, i=i: x + i for i in range(3)]
[f(10) for f in funcs]      # [10, 11, 12]
```

Alternatively, use [`functools.partial`](https://docs.python.org/3/library/functools.html#functools.partial), which is clearer about intent:

```python
from functools import partial

def add(x, i):
    return x + i

funcs = [partial(add, i=i) for i in range(3)]
[f(10) for f in funcs]      # [10, 11, 12]
```

## Should you use map and filter with a lambda?

Usually not. Lambdas are often taught alongside `map` and `filter`, but in Python a comprehension usually reads better:

```python
nums = [1, 2, 3, 4, 5]

list(map(lambda x: x ** 2, nums))              # [1, 4, 9, 16, 25]
[x ** 2 for x in nums]                          # same, more idiomatic

list(filter(lambda x: x % 2 == 0, nums))        # [2, 4]
[x for x in nums if x % 2 == 0]                 # same, more idiomatic
```

`map` earns its place when you already have a named function and no transformation to express:

```python
lines = [" a ", "b  "]
list(map(str.strip, lines))       # ['a', 'b'] - no lambda needed
list(map(int, ["1", "2"]))        # [1, 2]
```

## When is operator.itemgetter better than a lambda?

Whenever the lambda only fetches an item or an attribute. For simple attribute or index access, the [`operator` module](https://docs.python.org/3/library/operator.html) is clearer, and the sorting HOWTO describes its accessors as easier and faster:

```python
from operator import itemgetter, attrgetter

sorted(people, key=itemgetter(1))               # instead of lambda p: p[1]
sorted(employees, key=attrgetter("salary"))     # instead of lambda e: e.salary
sorted(records, key=itemgetter("score", "name"))  # multi-key
```

## Are lambdas slow in pandas apply?

The lambda itself is not slow, but `apply` with a lambda runs Python code once per row. On large frames, therefore, use vectorised operations instead:

```python
# slow on a million rows
df["total"] = df.apply(lambda r: r["price"] * r["qty"], axis=1)

# fast - vectorised
df["total"] = df["price"] * df["qty"]

# conditional, vectorised
import numpy as np
df["band"] = np.where(df["score"] > 80, "high", "low")
```

The lambda is not the problem; row-wise Python is. Our [NumPy broadcasting guide](https://www.1stepgrow.com/articles/numpy-broadcasting) covers why vectorisation is so much faster.

## Common mistakes with lambdas

- **Assigning a lambda to a name** instead of writing `def`.
- **Late binding in loops**, which gives every lambda the final loop value.
- **Wrapping an existing function**, as in `key=lambda s: s.lower()` when `key=str.lower` works.
- **Cramming conditionals and arithmetic** into one unreadable line.
- **Using `apply` with a lambda on large DataFrames** when a vectorised expression exists.

## When should you use a Python lambda function?

Use a Python lambda function when it is an argument to another function and the logic fits comfortably on one line. Everywhere else, use `def`.



## Related reading

[User-defined functions](https://www.1stepgrow.com/articles/python-functions) covers function design properly. [Iterators and generators](https://www.1stepgrow.com/articles/python-iterators-generators) covers the lazy-evaluation companion.

## Frequently asked questions

### What is the difference between lambda and def?

A lambda is an expression that produces a function, limited to a single expression with an implicit return. def is a statement that can contain any code, accepts annotations and a docstring, and gives the function a real name that appears in tracebacks. Both create the same kind of function object, so anything a lambda does, def can do too.

### Should I assign a lambda to a variable?

No. PEP 8 says to always use a def statement instead of binding a lambda directly to a name. You get the same function object with a meaningful __name__ for debugging, and you keep the option to add a docstring and type hints later. Named lambdas save one line and cost clarity, so they are a habit worth dropping.

### Why do lambdas in a loop all return the same value?

Because closures look up variables when the function runs, not when it is defined. All the lambdas share the loop variable, which holds its final value by the time they are called. Bind the current value with a default argument, as in lambda x, i=i: x + i, or use functools.partial. Functions defined with def in a loop behave the same way.

### Are lambdas slower than regular functions?

No. A lambda and an equivalent def compile to essentially the same bytecode, so calling them costs the same. Any perceived difference comes from how they are used: calling a Python function once per row over a large DataFrame is slow whether it is a lambda or a def, and vectorised operations are the real fix.

---
_Source: 1stepGrow (https://www.1stepgrow.com/articles/python-lambda-functions/). Cite with the title, "1stepGrow" and a link._
