Lambda Expressions as Arguments to Algorithms
A lambda expression is a notation that generates a function object, similar in kind to writing Less_than<int>{x}.
Try it: Functions as Values: Lambda
That a lambda (like def) evaluates to a function object that a name can refer to and that can be passed to other functions — apply(f, x), twice(f, x), map(f, xs), sorted(xs, key=f) — and that each call is traced by binding the argument, substituting it into the single expression and evaluating in operator order.
How it works
- lambda x: expression evaluates to a function object (named <lambda>); nothing inside it is computed yet. f = ... gives it a name, exactly as def f(x): return expression would.
- Writing f without parentheses passes the function object itself; inside apply or twice the parameter func refers to the same object.
- A call binds the argument to the parameter x, substitutes it into the expression, and evaluates by operator precedence (* and % before + and -, parentheses first).
- The value of the expression is the call's return value; there is no other body.
- map(f, xs) calls f once per item and keeps the results in order; sorted(xs, key=f) calls f once per item to get keys, orders by the keys (ties keep their order) and returns the items.
Default run (9 steps): The program is ready: a lambda written inline, then f(x). … print shows 7.
Simplified: Four one-parameter lambdas with editable integer constants, small int arguments (at most 6 items). Each substituted expression the lab shows was evaluated by CPython 3.12 and has the same value as the real call; printed results and ZeroDivisionError messages match CPython.
Loading the simulation…
A Small Function Written Where It Is Needed
Many useful functions are small and short-lived. You write one to hand to an algorithm, to filter a collection, or to sort some items, and then you never need it again. Writing a full named function for each of those jobs is heavy. A lambda expression lets you write the function right where it is needed.
The harder question is what such a small function is allowed to see. Its body may refer to names that were declared in the surrounding code. Keep one question in mind as you read: when the lambda's body mentions a local name from the place where the lambda was written, does the function work with that name itself, or with a separate value it has been given? The capture list is the part of the lambda that answers this question, and the rest of this article is about reading it.
What a Lambda Expression Produces
A lambda expression is a notation that generates a function object. It is similar in kind to writing Less_than<int>{x}, which is a function object constructed with x as its stored value. So a lambda is not a special kind of call. It is a compact way of producing an object that can be called with an int and that returns the result of a comparison.
true- The square brackets [&] form the capture list, which comes first.
- The parenthesized (int a) is the parameter list the object is called with.
- The name
less_than_xholds the function object that the lambda generated. - Calling
less_than_x(5)runs the body a < x using the parameter a set to 5.
The call less_than_x(5) returns true because 5 is less than the outer x, which is 10. The body reaches x even though x was declared outside the lambda, and the capture list is what makes that possible.
Following one call to a lambda's function object
Given int x = 10 and auto less_than_x = [&](int a){ return a < x; };, what does less_than_x(7) return?
- 1Identify the stored object
less_than_xis the function object the lambda generated. Calling it runs the lambda body. - 2Bind the parameter
The parameter a receives the argument 7 for this call.
- 3Reach the outer name
The body mentions x. The capture list [&] lets the body reach the outer x by reference, so its current value 10 is used.
- 4Evaluate the comparison
The body computes 7 < 10, which is true.
true
Reading the Capture List
A capture list always sits first, inside square brackets, before the parameter list. Once you see the pattern, the rule is simple. A bare ampersand or equals sign applies to every local name the body uses. An ampersand or equals sign followed by a name applies only to that one name. The ampersand means the body works through a reference to the original. The equals sign means the generated object receives a copy of the value. Empty brackets mean the body is given no local names at all.
Read the capture list from left to right. The first symbol tells you the access mode, and the name after it tells you which local the rule covers. A symbol with no name covers every local the body uses. The table below summarizes the forms the source describes.
| Capture list | How the body reaches the names it uses |
|---|---|
| [&] or [&x] | Through a reference to the original local |
| [=] or [=x] | Through a copy of the value, held by the generated object |
| [ ] | No local names are captured, so the body cannot use outside locals |
Capture list forms and their access modes
Reference Versus Copy in Practice
Suppose a program has a local variable x and wants two function objects that compare an incoming int against it. One lambda should refer to x directly, and the other should carry its own copy. The two lambdas look nearly identical, so the capture list is the only place where their behavior is decided. The difference matters whenever the surrounding code's value of x and the object's view of x could diverge.
by_ref(15): true
by_val(15): falseby_refuses [&], so its body works through a reference to the original x.by_valuses [=], so the generated object holds its own copy of the value of x.- x is changed to 20 after both lambdas are created.
by_ref(15) compares 15 with the current x, which is 20.by_val(15) compares 15 with the copy it holds, which is 10.
What do you think happens?
After x is changed to 20, does by_val(15) return true or false?
Reveal answer
false
by_val holds its own copy of x, which was 10 when the lambda was written. The change to 20 does not reach that copy, so 15 < 10 is false.
The syntax of the two lambdas is identical except for the ampersand or equals sign. A reference capture always lets the body see the current state of the original name. A value capture lets the object keep its own copy, separate from the original. Reading the capture list carefully is therefore the habit that tells you which of the two behaviors you will get.
Choosing a Capture Mode
There are four choices to weigh when you write a lambda: capture nothing, capture all used locals by reference, capture all used locals by value, or capture a single named local. Each one changes which outside names the body can reach and how it reaches them. The diagram below lays the four options side by side, grouped by access mode.
Use [ ] when the body needs no outside local names. The source identifies this as the form to use in that case, because the lambda then depends on nothing declared around it. Use [&] or [&x] when the body must see the current state of an outer name. Use [=] or [=x] when the object should keep its own copy that does not follow later changes to the original.
Mistakes With Capture Lists
Common mistake Assuming a value capture shares the original variable
What it looks likeauto f = [=](int a){ return a < x; }; followed by a change to x
Why it failsThe equals sign gives the generated object a copy of the value, so the object does not follow later changes to x.
Do this insteadUse [&] or [&x] when the body must see the current state of the original name.
Common mistake Leaving the capture list empty while the body uses an outer name
What it looks likeauto f = [](int a){ return a < x; }; where x is a local
Why it failsEmpty brackets mean the body is given no local names at all, so x is not reachable from the body.
Do this insteadAdd the name to the capture list, for example [&x] or [=x].
Common mistake Judging behavior from the body alone
What it looks likeTwo lambdas with identical bodies, one with [&] and one with [=]
Why it failsThe bodies match, but the capture list decides which x each one uses.
Do this insteadAlways read the capture list first and ask which x the body will use.
Practice Reading Capture Lists
Given int x = 10; and auto f = [=](int a){ return a < x; };, then x is changed to 20. Does f(15) return true or false, and what does the capture list tell you about the value f uses?
Hints
- Look at the bare equals sign in the capture list. What does it give the generated object?
- Compare the copied value of x with the new value of x after the change.
- The answer should be the same as the
by_valcase in the earlier example.
Write a lambda that returns true when its argument is greater than an outer local called limit, and pick the capture list so that the lambda sees later changes to limit. Then explain in one sentence why your choice works.
Hints
- Pick the capture form that refers to the original rather than a copy.
- Think about whether [&limit] or [=limit] would follow a later change to limit.
Key Takeaways
- A lambda expression is a notation that generates a function object, similar in kind to writing Less_than<int>{x}.
- The capture list sits first in square brackets and decides how the body reaches outside local names.
- A bare ampersand or equals sign covers every local the body uses; a symbol followed by a name covers only that one name.
- A reference capture sees the current state of the original, while a value capture keeps the generated object's own copy.
- Use [ ] when the body needs no outside locals, and choose [&] or [=] (or a single-name form) based on whether the object should follow later changes.