Skip to content

Lambdas & Higher-Order Functions Interview Questions & Answers

15 questions Updated 2026-06-18 Share:

Python interview questions on lambda syntax and limitations, lambda vs def, higher-order functions, the key argument in sorted/max/min, and functions as first-class objects.

Read the in-depth guidePython Lambdas and Higher-Order Functions Explained — key=, First-Class Functions(opens in new tab)
15 of 15

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 ways to practice

The self-quiz is live. Join our channel for updates, new content & tech tips.

Join our WhatsApp Channel