A C++ lambda is short to write, but compact enough to hide three separate ideas: the callable itself, the values it captures, and the moment those values are copied or referenced. Get that third idea wrong and the same predicate counts three passing marks where you expected two, with no warning from the compiler. A lambda is most useful when a small operation belongs beside the code that uses it. For structured language learning, follow the C++ programming course.
What a C++ lambda is and why it exists
A lambda is an unnamed callable object written at the point of use. Callable means it can be invoked with arguments. Compare these alternatives:
bool isAdult(int age) { return age >= 18; }
auto isAdult = [](int age) { return age >= 18; };In separate programs, either returns false for 16 and true for 21. Prefer a lambda for a local operation passed to an algorithm or callback. Prefer a named function when behaviour is widely reused or deserves its own domain name.
Lambdas entered C++ in C++11. They are not macros or merely inline functions: each expression creates a distinct callable object type.
Read lambda syntax from left to right
Start with int minimum = 50; and read this expression in four parts:
auto passes = [minimum](int value) -> bool {
return value >= minimum;
};The capture list [minimum] stores the outside value. (int value) receives the argument, -> bool states the return type, and the braces hold the body. Thus passes(67) returns true.
C++ can infer the return type, making [minimum](int value) { return value >= minimum; } equivalent. Square brackets remain compulsory with no capture. An immediate call puts arguments after the body: [](int a, int b) { return a + b; }(7, 5) evaluates to 12.
Capture by value and capture by reference
This program exposes the difference:
#include <iostream>
int main() {
int passMark = 50;
auto byValue = [passMark](int mark) { return mark >= passMark; };
auto byReference = [&passMark](int mark) { return mark >= passMark; };
passMark = 70;
std::cout << byValue(67) << ' ' << byReference(67);
}The output is 1 0. byValue retained 50, so 67 >= 50 is true. byReference sees 70, so 67 >= 70 is false.
Capture | Can use outside local names? | Stored relationship |
|---|---|---|
| No | No captured state |
| Only | Owns a copy of |
| Only | Refers to the existing |
| Yes, when used | Copies used names by default |
| Yes, when used | Refers to used names by default |
Explicit captures are clearer because [=] and [&] can conceal dependencies. A copy can outlive its original variable. A reference is valid only while its object remains alive.

Fully worked example with std::count_if
Here the outside threshold changes after creation:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> marks{48, 67, 72, 39, 85};
int passMark = 50;
auto passed = [passMark](int mark) { return mark >= passMark; };
passMark = 70;
int count = std::count_if(marks.begin(), marks.end(), passed);
std::cout << "Passed: " << count;
}The predicate still compares against its captured threshold 50:
Mark | Test | Result |
|---|---|---|
48 |
| false |
67 |
| true |
72 |
| true |
39 |
| false |
85 |
| true |
Three calls return true, so the exact line is Passed: 3. The outer assignment did not alter the copy.
Now replace [passMark] with [&passMark]. Against 70, 48, 67 and 39 are false, while 72 and 85 are true. The exact line becomes Passed: 2.
Stateful, initialised and generic lambdas
A value-captured copy cannot normally change inside the lambda. mutable permits that private change:
int calls = 0;
auto next = [calls]() mutable { return ++calls; };
int first = next();
int second = next();
std::cout << first << ' ' << second << ' ' << calls;The output is 1 2 0. The captured copy moves from 0 to 1 to 2 across the two calls, while outer calls stays 0.
An initialised capture computes state at creation:
int base = 10;
auto add = [offset = base + 5](int x) { return x + offset; };
base = 100;
std::cout << add(7);The output is 22: offset became 15, then 7 + 15 = 22. Initialised captures are a C++14 feature.
A generic lambda uses auto: auto triple = [](auto x) { return x * 3; };. This C++14 feature makes triple(4) return 12 and triple(2.5) return 7.5.
Three practical STL patterns
For filtering or counting, std::count_if receives a predicate without knowing the pass rule. That separation makes lambdas a natural STL fit.
For sorting, std::sort takes the comparator as its third argument. Let Candidate contain a std::string name and int score. Given {{"Neha", 72}, {"Arun", 88}, {"Meera", 81}}, this comparator sorts descending:
[](const Candidate& a, const Candidate& b) {
return a.score > b.score;
};The order is Arun 88, Meera 81, Neha 72. A lambda remains an object with behaviour, linking it to OOP concepts for CS exams.
For transforming, std::transform runs the lambda over every element and writes each result into an output range. With std::vector<int> prices{120, 80, 200}; and int discount = 10 captured by value, applying price - price * discount / 100 gives {108, 72, 180} through 120 - 12, 80 - 8, and 200 - 20. For using idioms like this under time pressure, see Coding Round Strategy.
Common lambda errors and how questions test them
Missing capture: using
passMarkinside[]causes a compile error. Write[passMark]or[&passMark].Changing a value copy: modification without
mutablefails because the call operator is non-mutating by default. Addmutableonly for intentional private state.Dangling reference: storing a lambda that references a dead local leaves an invalid reference. Capture the value or redesign ownership.
Invalid comparator:
return a.score >= b.score;says each of two candidates scoring88comes before the other. This violatesstd::sort's strict ordering. Use>and add a tie-break only if needed.
Exam questions on lambdas reduce to three asks: predict 1 0 for a value-versus-reference pair, distinguish Passed: 3 from Passed: 2 when the outer variable changes after the lambda is created, and say why >= breaks the strict ordering std::sort requires.
Short version, exercises and the next step
A lambda is a local callable. Its capture list decides which outside state becomes part of that callable. Value versus reference capture determines whether later outside changes are visible.
Count values at least
10in{5, 12, 9, 20}with a captured cutoff. Answer:2, from12and20.Predict
int n = 4; auto twice = [n] { return n * 2; }; n = 7; std::cout << twice();. Answer:8, becausenwas copied as4.Sort
{"pear", "kiwi", "fig"}by length, breaking equal lengths alphabetically. Answer:fig,kiwi,pear. Compare.size()first, then usea < bwhen lengths match.
For broader application, continue with Coding for Placement, or browse the Coding Skills learning path. Trace each capture, run the snippets, and predict every output before moving on.




