A lambda is an anonymous, single-expression function:
lambda args: expression. It returns the expression's value automatically
(no return). Its limitation is exactly that — it can hold only one
expression, no statements, assignments, loops, or annotations.
square = lambda x: x * x
square(5) # 25
add = lambda a, b=1: a + b # defaults allowed
add(10) # 11
# NOT allowed: lambda x: (y = x; return y) — no statements
Because it's an expression, a lambda can be passed inline wherever a
function is expected. Keep them short; anything needing multiple lines or a
docstring should be a named def.
Use a lambda for a tiny throwaway function passed inline as an
argument (a sort key, a callback). Use def for anything reused, named,
documented, or non-trivial. A named function gives a useful __name__ in
tracebacks; a lambda just shows <lambda>.
# good lambda: inline, one-off
sorted(words, key=lambda w: len(w))
# prefer def: reused / needs a name
def by_length(w):
return len(w)
PEP 8 even discourages assigning a lambda to a name
(f = lambda x: ...) — if you need a name, just use def. Lambdas shine as
arguments, not as definitions.
A higher-order function is one that takes a function as an argument
and/or returns a function. They enable composing and parameterizing
behavior. Built-in examples include map, filter, sorted, and the
functools tools.
def apply_twice(fn, x):
return fn(fn(x)) # takes a function
apply_twice(lambda n: n + 3, 10) # 16
list(map(str.upper, ["a", "b"])) # ['A', 'B']
list(filter(lambda n: n > 0, [-1, 2, -3, 4])) # [2, 4]
Higher-order functions are the foundation of functional-style Python and of decorators (which both take and return functions). They let you pass behavior around as data.
key takes a function applied to each element to derive the value used
for comparison — the elements themselves aren't changed, only how
they're ranked. It's used by sorted, list.sort, max, and min.
words = ["banana", "kiwi", "apple"]
sorted(words, key=len) # ['kiwi', 'apple', 'banana']
max(words, key=len) # 'banana'
sorted(words, key=str.lower) # case-insensitive
people = [("ada", 36), ("grace", 45)]
max(people, key=lambda p: p[1]) # ('grace', 45)
key is called once per element (efficient), unlike the old cmp
style. For multi-level sorts, return a tuple:
key=lambda p: (p.last, p.first).
In Python, functions are first-class objects: they can be assigned to variables, stored in data structures, passed as arguments, and returned from other functions — just like any value. This is what makes higher-order functions and closures possible.
def shout(s): return s.upper() + "!"
f = shout # assign to a variable
f("hi") # 'HI!'
dispatch = {"loud": shout} # store in a dict
dispatch["loud"]("hey") # 'HEY!'
def make_op(op): # return a function
return (lambda a, b: a + b) if op == "+" else (lambda a, b: a - b)
make_op("+")(2, 3) # 5
Treating functions as values enables strategy/dispatch tables, callbacks, and decorators. There's no separate "function pointer" concept — the function is the object.
A lambda's body is a single expression whose value is returned implicitly
— it can't hold statements like assignments, for, try, or return. This
keeps lambdas small; anything needing statements should be a def.
square = lambda x: x * x # fine, an expression
f = lambda x: (x += 1) # SyntaxError: assignment is a statement
# use a def instead when you need statements
Rule of thumb: if a lambda won't fit in one expression, write a named def.
Yes — within the single-expression rule. A conditional expression
(a if cond else b) and the walrus := are expressions, so they're
allowed; full if/elif blocks are not.
grade = lambda s: "pass" if s >= 60 else "fail"
f = lambda xs: (n := len(xs)) and sum(xs) / n # uses walrus
Rule of thumb: ternaries and := are fine in a lambda; branching logic beyond
that wants a def.
Because name = lambda ... gives you a function with the generic name
<lambda> (worse tracebacks, no clear identity) yet none of the benefits of
def. If you need a name, use def; reserve lambdas for anonymous,
inline use.
f = lambda x: x + 1 # PEP 8 flags this
def f(x): return x + 1 # preferred when naming
Rule of thumb: lambdas for throwaway inline callables; def whenever the
function gets a name.
Short inline callables passed to higher-order functions: a key= for
sorted/max/min, a quick predicate for filter, a callback for GUI
events, or a default factory. They shine when the logic is trivial and used
once.
sorted(words, key=lambda w: len(w))
max(items, key=lambda x: x.score)
button.on_click(lambda e: save())
Rule of thumb: use a lambda when a one-line function is needed right where it's passed.
Yes — a lambda is just an anonymous function and forms a closure with the same late binding. In a loop, all lambdas share the loop variable's final value; bind it with a default argument to capture per-iteration.
handlers = [lambda: i for i in range(3)] # all -> 2
handlers = [lambda i=i: i for i in range(3)] # -> 0, 1, 2
Rule of thumb: lambdas inherit closure/late-binding rules; use x=x defaults
to freeze loop values.
For common "get a field / apply an operator" keys, operator.itemgetter,
attrgetter, and methodcaller are faster and clearer than equivalent
lambdas (they're C-implemented and picklable). Reach for them in sorted,
max, map, etc.
from operator import itemgetter, attrgetter
sorted(rows, key=itemgetter(1)) # vs lambda r: r[1]
sorted(people, key=attrgetter("age")) # vs lambda p: p.age
Rule of thumb: prefer operator.* helpers over lambdas for plain
field/index/method access.
A higher-order function can take or return functions. Returning one (a closure/factory) lets you build customized callables at runtime — the basis of decorators, partial application, and strategy patterns.
def multiplier(n):
return lambda x: x * n # returns a function
triple = multiplier(3)
triple(10) # 30
Rule of thumb: returning functions enables configuration-by-closure; decorators are the most common example.
You can, but a comprehension is usually clearer when the transform is an
expression. Lambdas with map/filter add visual noise; map/filter are
best with existing named functions.
list(map(lambda x: x * 2, xs)) # noisy
[x * 2 for x in xs] # clearer
list(map(str.upper, words)) # good: named function
Rule of thumb: comprehension over map/filter+lambda; use map/filter
with named functions.
Because functions are objects, you can store them in data structures, pass them as callbacks/strategies, build dispatch tables, and return them from factories. A dispatch dict replaces long if/elif chains cleanly.
ops = {"+": lambda a, b: a + b, "-": lambda a, b: a - b}
ops["+"](2, 3) # 5 -> dispatch table
Rule of thumb: use function objects in dicts/lists to replace branching with lookup-and-call.
Define __call__ so instances are callable. Unlike a plain function
or lambda, a callable object can carry state and configuration on self
across calls — useful for stateful strategies, accumulators, or configurable
transforms.
class Adder:
def __init__(self, n): self.n = n
def __call__(self, x): return x + self.n
add5 = Adder(5)
add5(10) # 15
Rule of thumb: use __call__ when you need a "function" that also keeps
mutable state or configuration.
More Functions interview questions
More ways to practice
The self-quiz is live. Join our channel for updates, new content & tech tips.