skip to content

Lambda and Higher-Order Functions

Functions are ordinary objects: you can pass them, return them and write them inline. This group covers the idiomatic ways to do that, and when a comprehension reads better than map or filter.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

19

Why are Python functions called first-class objects, and what does `def` create?

level: juniorimportance: must knowfreq 68%

answer

  1. def runs; it does not declare
  2. the name is only a binding
  3. an object with attributes and a dict
  4. types.FunctionType, same as a lambda
  5. store it, pass it, return it

basics

~20 s

A def statement runs at runtime, builds a function object and binds it to a name, exactly like an assignment. That object can be stored in a dict, passed as an argument or returned again, which is what first-class means.

solid answer

~40 s

`def` is an executable statement, not a declaration. When it runs, the interpreter builds a function object and binds it to the `def`'s name in whatever namespace is executing — module globals, a class body, or a local scope. The result is an ordinary object of type `types.FunctionType`, carrying `__name__`, `__qualname__`, `__doc__`, `__module__` and a writable `__dict__`. Because it is just a value, it can be aliased, stored in a list or dict, passed to another function and returned from one — that is all "first-class" means. The bare name is the object; the parentheses are the call, which is why `register(rebuild())` registers a return value rather than the function. `callable(obj)` reports whether an object's type supports call syntax, so classes and C builtins qualify too.

code

python · 12 lines
python
import types

def shout(text):
    return text.upper() + "!"

def whisper(text):
    return text.lower() + "..."

styles = {"shout": shout, "whisper": whisper}
chosen = styles["shout"]
print(chosen("rebuild finished"))
print(callable(chosen), isinstance(shout, types.FunctionType))

go deeper

for a junior

Be ready to say what a def produces and to show a function stored in a dict or handed to another function. Recall that the bare name is the object and that adding parentheses calls it immediately.

for a middle

Explain that def executes and binds like an assignment, name the type behind a function object and the attributes it carries, and describe exactly what callable() interrogates.

for a senior

Show where first-class functions earn their keep in real code — callbacks, plugin registries, dispatch tables — and be honest about the traceability you lose when behaviour arrives as a value.

for a principal

Own the tradeoff: passing functions around makes behaviour configurable at runtime but moves control flow out of the reader's line of sight. Set the team convention for when that indirection is worth its cost.

### `def` is a statement, and statements execute Python has no separate declaration phase for functions. A `def` is an ordinary statement that runs when control reaches it. When it runs, the interpreter builds a **function object** from the body the compiler already prepared, fills in the defaults and the enclosing-scope information, and then **binds that object to the `def`'s name** in whichever namespace is currently executing — module globals at import time, a class namespace inside a class body, a local scope inside another function. The binding is an ordinary name binding: nothing about it is more magical than `x = 5`. Two consequences follow immediately. First, a `def` inside an `if` that never runs never creates anything; second, `del f` or `f = None` removes the *name*, not some registered declaration. ### What the object actually is The object is an instance of `types.FunctionType` — the same type a `lambda` produces, which is why `types.LambdaType is types.FunctionType`. It carries attributes you can read at runtime: - `__name__` — the bare name it was defined with. - `__qualname__` — the dotted path to it inside its module. - `__doc__` — the docstring, or `None`. - `__module__` — the name of the defining module. - `__defaults__` — the default values, evaluated once when the `def` ran. - `__dict__` — a writable per-object attribute dict. Functions written in C are a *different* type: `len` is a `types.BuiltinFunctionType`, and `inspect.isfunction(len)` is `False` while `inspect.isbuiltin(len)` is `True`. That distinction matters the moment you introspect: a C builtin has no writable `__dict__` and may not expose a signature to `inspect.signature`. ### "First-class" is a claim about what you may do with the value An object is first-class in a language when it can be treated like any other value. For Python functions that means all of the following are unremarkable: - **Bind it to another name.** `alias = rebuild` — one object, two names. - **Put it in a container.** A list of validators, a dict mapping a mode string to a handler, a tuple of hooks. - **Pass it as an argument.** Every `key=` argument, every callback, every `filter` predicate is this. - **Return it from a function.** A function that builds and returns another function is the whole basis of decorators and factory helpers. - **Attach data to it.** Because `__dict__` is writable, `handler.priority = 3` sticks. Notice that no special syntax is required to *refer* to a function without calling it. **The bare name is the object; the parentheses are the call.** This is the single most common beginner bug in this area: writing `register(rebuild())` registers whatever `rebuild` returned — very often `None` — instead of registering the function. The symptom appears far from the cause, when something later tries to call the stored `None`. ### `callable` and the wider family of callables `callable(obj)` reports whether `obj`'s **type** supports call syntax. It asks the type, not the instance, and it is a capability check rather than a promise: `callable(f)` being `True` says nothing about whether `f()` will raise for wrong arity. Function objects are callable; so are C builtins; so are classes, because calling a class constructs an instance; and so are instances of a class that defines the call protocol. `callable(3)` is `False`. The practical reading is that "a function" in a Python API contract almost always means "any callable". Code that insists on `types.FunctionType` via `isinstance` rejects perfectly good callables — a class, a partial application built with `functools.partial`, a callable instance — and is usually a design mistake. Duck-type on `callable` if you must check at all. ### Why this shows up in interviews Everything a Python codebase does with behaviour-as-data rests on this one fact. Sorting with a `key=` argument, wiring a callback into an event source, building a plugin registry, mapping a command name to the routine that implements it, memoising, retrying, timing — each of them passes, stores or returns a function object. A candidate who thinks of `def` as a declaration cannot explain any of them, and will reach for strings plus `eval`, or for an ever-growing conditional, where a Python programmer reaches for a dict of functions. ### Introspecting one in practice `inspect` is the readable front door: `inspect.isfunction(obj)` distinguishes a Python-level function, `inspect.signature(obj)` reports the parameters, and reading `__module__` together with `__qualname__` gives you the path you would put in a log line. All of it is plain attribute access on an ordinary object — which is the point.

  • What does the builtin `callable(obj)` actually tell you?
    It reports whether `obj`'s type supports call syntax — it interrogates the type, not the instance. Function objects, C builtins, classes (calling a class constructs an instance) and instances of a class defining the call protocol all return True; an `int` or a `list` returns False. It is a capability check, not a promise: `callable(f)` says nothing about whether `f()` will raise for the wrong arity.
  • How does `types.FunctionType` differ from `types.BuiltinFunctionType`?
    `types.FunctionType` is the type of a function written in Python; `type(f)` is that for both a `def` and a `lambda`, since `types.LambdaType` is the same object. Functions implemented in C, such as `len`, are `types.BuiltinFunctionType` — `inspect.isfunction(len)` is False and `inspect.isbuiltin(len)` is True. The practical difference is introspection: a C builtin has no writable `__dict__` and may not expose a signature.
  • How do you refer to a function without calling it, and what is the usual mistake?
    The bare name is already the object: `register(rebuild)` passes the function, `register(rebuild())` calls it first and passes whatever it returned, very often `None`. The failure surfaces far from the cause, when something later tries to call the stored value and raises `TypeError: 'NoneType' object is not callable`. Whenever a callback, a `key=` argument or a registry entry misbehaves, check for stray parentheses first.

A function name is a label on a jar, not the jar itself: you can hand someone the jar, put it on a shelf beside others, or write a new label for the same jar — opening it is a separate act.

saying these in an interview costs you the question

  • Claims def is a declaration processed before the module body runs
  • Thinks a function name is special syntax rather than an ordinary binding
  • Says only lambdas can be passed around as values
  • Passes handler() instead of handler and stores the return value
  • Believes callable() guarantees the call will succeed
  • Assumes a C builtin like len behaves identically to a def'd function

context

open as a page

What does operator.itemgetter(2, 0) return when called on a sequence?

level: juniorimportance: must knowfreq 45%

basics

~10 s

It returns the two-element tuple (row[2], row[0]), in the order the indices were given. With a single index, operator.itemgetter returns that one item itself, not a one-element tuple.

open as a page

What does the key= argument of Python's sorted() do?

level: juniorimportance: must knowfreq 72%

basics

~10 s

key= takes a one-argument callable applied to every element; sorted, min and max then compare the values it returns instead of the elements themselves. The elements come back unchanged, only reordered.

open as a page

What is a Python lambda expression, and how does it differ from a def function?

level: juniorimportance: must knowfreq 78%

basics

~20 s

A lambda expression builds an anonymous function whose body is a single expression, returned implicitly. def is a statement that names the function and can contain any block of code. Both create the same kind of function object.

open as a page

What do Python's built-in map() and filter() return, and how do you get a list?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Both return a lazy, one-shot iterator object, not a list. Nothing is computed until you iterate. Wrap the result in list() or tuple(), or feed it straight to a for loop, sum(), any() or all().

open as a page

Why is `callable(x)` preferred over `isinstance(x, types.FunctionType)` in Python?

level: middleimportance: should knowfreq 40%

basics

~10 s

callable(x) asks whether type(x) defines __call__, so it accepts classes, built-in functions, bound methods, functools.partial objects and instances with __call__. types.FunctionType matches only def and lambda objects, rejecting most callables real code passes around.

open as a page

How does operator.attrgetter('user.plan.name') resolve its dotted path?

level: middleimportance: should knowfreq 38%

basics

~20 s

It walks the path one segment at a time with repeated attribute lookups at call time, equivalent to obj.user.plan.name. A dot inside the string means traversal, never an attribute whose own name contains a dot.

open as a page

How many times does sorted() call the key= function, and why does it matter?

level: middleimportance: should knowfreq 48%

basics

~20 s

Exactly once per element: n calls for n elements. CPython computes every key up front into an internal array and then compares only those key values, so an expensive key costs O(n), not O(n log n).

open as a page

Why can a Python lambda body contain no statements, and what can you use instead?

level: middleimportance: should knowfreq 52%

basics

~20 s

The grammar defines a lambda body as a single expression, and statements such as assignment, return, raise, with and try are not expressions. Use a conditional expression, an assignment expression, or a helper call — or write a def.

open as a page

How does Python's map() behave when given several iterables of different lengths?

level: middleimportance: should knowfreq 35%

basics

~20 s

map() takes one item from each iterable per step and calls the function with that many arguments. It stops silently as soon as the shortest iterable runs out, so extra items in the longer ones are never seen.

open as a page

What does the initializer argument to functools.reduce() change about the result?

level: middleimportance: should knowfreq 45%

basics

~20 s

It is the starting accumulator, combined before the first item. It seeds the result type, makes an empty iterable return it rather than raise TypeError, and forces the function to run even for a one-element input.

open as a page

When does a dict of functions beat an if/elif chain for dispatch in Python?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A dict mapping keys to function objects makes branch selection one lookup, keeps handlers independently testable, and turns an unknown key into a loud KeyError instead of a silent fall-through. Keep if/elif when conditions are not equality on one key.

open as a page

Why can operator.itemgetter cross a process boundary when an equivalent lambda cannot?

level: seniorimportance: should knowfreq 35%

basics

~20 s

An operator.itemgetter is an instance of a real, importable type that knows how to rebuild itself from its stored indices, so pickle can serialize it. Pickle stores plain functions by qualified name, and an anonymous function has no importable name to look up.

open as a page

How do you make sorted() order one field descending and another ascending?

level: seniorimportance: should knowfreq 45%

basics

~10 s

reverse= flips the whole ordering, never one field. Either negate the numeric field inside a tuple key, as in key=lambda r: (-r.score, r.name), or run two stable sorts, least significant field first.

open as a page

Why does a traceback frame read '<lambda>', and what does PEP 8 advise about it?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Every lambda records the literal name '<lambda>', so tracebacks, logs and profiles cannot tell one from another. PEP 8 says that binding a lambda to a name with = should be a def instead, which restores the real name.

open as a page

When is map() or filter() clearer than the equivalent comprehension in Python?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Use map() or filter() when the callable is already named: map(str.strip, lines) reads well. Once you write a lambda inside them, a comprehension reads better. Where an aggregate such as sum or any fits, it beats both.

open as a page

Why can you set `f.calls = 0` on a Python function but not on an `int`?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

Function objects carry an ordinary instance dict, so arbitrary attributes stick to them just as they do on a normal instance. int and most builtin types have a fixed C-level layout with no dict, so the assignment raises AttributeError.

open as a page

What does operator.methodcaller('rjust', 8, '0') do when applied to a string?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

It returns a callable that runs s.rjust(8, '0') on whatever string you pass it, so '42' becomes '00000042'. The method name and its arguments are fixed when the methodcaller is built; the method itself is looked up on each operand at call time.

open as a page

What parameter forms are allowed in a Python lambda's parameter list?

level: middleimportance: nice to knowfreq 28%

basics

~10 s

Everything a def accepts except annotations: positional parameters, defaults, *args, **kwargs, keyword-only parameters after a bare *, and positional-only parameters before a /. The list is written without parentheses, and it may be empty.

open as a page