λ Lambda Expressions in C++ — Inline Functions with Captures
Master C++ lambda expressions — syntax, captures (by value vs reference), return types, generic lambdas and using lambdas with STL algorithms. With examples + interview Q&A.
A lambda is an inline, anonymous function — perfect for one-off callbacks passed to STL algorithms.
📜 Syntax
[capture](params) -> return_type { body }The return type is usually inferred — just write [capture](params) { body }.
🎒 Captures — borrowing variables from the enclosing scope
[]— capture nothing[x]— capture x by value (copy)[&x]— capture x by reference[=]— capture all by value[&]— capture all by reference[=, &x]— all by value except x by reference
📋 Real-world: STL algorithms
std::vector<int> v = {3, 1, 4, 1, 5, 9};
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
// v is now {9, 5, 4, 3, 1, 1}🌍 Generic lambdas (C++14+)
auto add = [](auto a, auto b) { return a + b; };
add(1, 2); // int
add(1.5, 2.5); // double
add("hi", ""s); // works for anything with operator+⚠️ Capture-by-reference lifetime trap
If a lambda captures by reference and outlives the captured variables, it has dangling references.
💻 Code Examples
Sort descending
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
for (int n : v) std::cout << n << ' ';Output:
5 4 3 1 1
Capture by value vs reference
int x = 10;
auto byVal = [x]() { return x + 1; };
auto byRef = [&x]() { return x + 1; };
x = 100;
std::cout << byVal() << ' ' << byRef();Output:
11 101
Count items matching a predicate
std::vector<int> nums = {1, 2, 3, 4, 5, 6};
int threshold = 3;
int count = std::count_if(nums.begin(), nums.end(),
[threshold](int n) { return n > threshold; });
std::cout << count;Output:
3
⚠️ Common Mistakes
- Capturing locals by reference and returning the lambda — dangling references when the locals die.
- Using `[=]` blindly and copying expensive objects — capture only what you need.
- Trying to capture `this` and modifying it without `mutable` — by-value captures are const by default.
- Confusing lambda type — each lambda has a unique anonymous type. Use `auto` or `std::function` to store them.
🎯 Interview Questions
Real questions asked at top product and service-based companies.
Q1.What is a lambda expression?Intermediate
An anonymous function defined inline. Syntax: `[capture](params) -> ret { body }`. Used as throwaway callbacks for STL algorithms, event handlers, and simple closures.
Q2.What's the difference between [=] and [&]?Intermediate
[=] captures all referenced variables BY VALUE (copies). [&] captures all BY REFERENCE. Value is safer (no dangling) but copies; reference is cheap but lifetime-fragile.
Q3.Can a lambda modify captured variables?Intermediate
By-reference captures: yes, naturally. By-value captures: no by default — the lambda's operator() is const. Add `mutable` to allow modifying the captured copies (without affecting the originals).
Q4.How is a lambda different from std::function?Advanced
Each lambda has a unique anonymous type — small, no allocation, inlined. std::function is a type-erasing wrapper that can hold any callable with a given signature — adds heap allocation and indirect call. Use `auto` for lambdas, `std::function` only when you need to STORE/PASS lambdas across boundaries.
Q5.What is a generic lambda?Advanced
C++14+ feature: parameters declared `auto` make the lambda's operator() a template. `auto add = [](auto a, auto b) { return a + b; };` — works for any addable types, like a one-shot function template.
🧠 Quick Summary
- Lambda = inline anonymous function with captures.
- Syntax: [capture](params) { body }.
- Capture by value [x] or reference [&x].
- [=] / [&] capture all referenced names.
- Use `auto` to store; `std::function` to type-erase across boundaries.
- Beware capture-by-reference lifetimes.