Question
What is a C++ lambda expression? Why does C++ include lambda expressions, what problems do they solve that were difficult or awkward before they were added, and how can a developer benefit from using them?
Please include one or two examples, especially showing when and where lambda expressions are appropriate.
Short Answer
By the end of this page, you will understand what a C++ lambda expression is, why it was added to the language, and when it is a better choice than a regular function or function object. You will also learn how capture lists work, how lambdas are commonly used with standard library algorithms, and what mistakes beginners often make when using them.
Concept
A lambda expression in C++ is a way to create a small anonymous function object directly where you need it.
In simple terms, a lambda lets you write function-like behavior inline instead of defining a separate named function or custom class.
Basic example:
auto add = [](int a, int b) {
return a + b;
};
Here, add is a callable object created from a lambda.
Why C++ added lambdas
Before lambdas, if you wanted to pass custom behavior to algorithms like std::sort, std::find_if, or std::for_each, you usually had to use one of these:
- a regular function
- a pointer to a function
- a custom functor class with
operator()
Those approaches worked, but they had limitations:
- Regular functions cannot easily use local variables from the surrounding scope.
- Function pointers are limited and less expressive.
- Functor classes are powerful, but often verbose for simple one-time logic.
Lambdas solve this by letting you:
- define behavior at the point of use
- capture local variables from the surrounding scope
- keep code shorter and easier to read
- avoid creating small throwaway classes or helper functions
What problem they solve
The biggest problem lambdas solve is passing custom logic that depends on nearby local state.
For example, suppose you want to count numbers greater than a local variable limit:
int limit = 10;
Before lambdas, it was awkward to pass limit into a comparison function for an algorithm. With a lambda, you can capture it directly:
int count = std::count_if(numbers.begin(), numbers.end(), [limit](int n) {
return n > limit;
});
That is concise, local, and easy to understand.
Why this matters in real programming
In real code, developers often need short custom actions for:
- sorting data
- filtering collections
- event handlers
- callbacks
- thread tasks
- deferred work
- custom predicates and transformations
Lambdas make these tasks much cleaner because the logic stays close to the place where it is used.
Important idea: lambdas are function objects
A lambda is not exactly a plain function. It creates a unique unnamed type that behaves like an object with operator().
This matters because:
- lambdas can store captured values as object state
- each lambda has its own type
- they are often more flexible than raw function pointers
So, a lambda is best understood as a compact way to define a callable object inline.
Mental Model
Think of a lambda as a temporary worker you hire for one small job.
- A regular function is like a permanent employee with a name and a fixed office.
- A functor class is like hiring someone but first building a full personnel file.
- A lambda is like saying: "For this one task, right here, do this exact thing."
The capture list is the information you hand to that worker before they start.
[x]means: give the worker a copy ofx[&x]means: let the worker use the originalx[=]means: copy everything needed[&]means: use everything needed by reference
This model helps explain why lambdas are so useful in algorithms: you can send a tiny piece of behavior together with the local data it needs.
Syntax and Examples
General syntax
[capture](parameters) -> return_type {
// body
}
In many cases, C++ can infer the return type, so -> return_type is optional.
Small example
#include <iostream>
int main() {
auto greet = []() {
std::cout << "Hello from a lambda\n";
};
greet();
}
What this does
[]is the capture list()is the parameter list- the body works like a function body
greetstores the lambda objectgreet()calls it
Example with parameters
#include <iostream>
int main() {
auto multiply = []( a, b) {
a * b;
};
std::cout << (, ) << ;
}
Step by Step Execution
Consider this example:
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {3, 8, 12, 5, 20};
int limit = 10;
int count = std::count_if(numbers.begin(), numbers.end(), [limit](int n) {
return n > limit;
});
std::cout << count << '\n';
}
Step-by-step
1. A vector is created
std::vector<int> numbers = {3, 8, 12, 5, 20};
The vector contains 5 values.
2. A local variable is created
limit = ;
Real World Use Cases
1. Custom sorting
You often need to sort objects by a specific field.
std::sort(users.begin(), users.end(), [](const User& a, const User& b) {
return a.age < b.age;
});
Useful in:
- user lists
- product catalogs
- leaderboard ranking
2. Filtering data
Lambdas are commonly used with algorithms like std::find_if, std::count_if, and std::remove_if.
auto it = std::find_if(tasks.begin(), tasks.end(), [](const Task& t) {
return t.completed;
});
Useful in:
- task apps
- log analysis
- data cleanup scripts
3. Callbacks and event handling
A lambda can define a small action to run when something happens.
button.onClick([]() {
std::cout << "Button clicked\n";
});
Real Codebase Usage
In real projects, lambdas are rarely used just because they are shorter. They are used because they help keep behavior close to where it is needed.
Common patterns
Guard-like checks inside algorithms
auto it = std::find_if(items.begin(), items.end(), [](const Item& item) {
return !item.name.empty();
});
This keeps simple conditions inline and readable.
Validation rules
auto hasInvalidUser = std::any_of(users.begin(), users.end(), [](const User& user) {
return user.id <= 0 || user.email.empty();
});
This pattern appears in data validation and API input checks.
Transforming collections
std::vector<int> lengths;
std::transform(words.begin(), words.end(), std::back_inserter(lengths), [](const std::string& word) {
return static_cast<int>(word.());
});
Common Mistakes
1. Forgetting to capture a variable
Broken code:
int limit = 10;
auto f = [](int x) {
return x > limit;
};
This fails because limit is not available inside the lambda.
Correct version:
int limit = 10;
auto f = [limit](int x) {
return x > limit;
};
2. Capturing by reference when the lambda lives longer than the variable
Broken idea:
auto makeLambda() {
int x = 5;
return [&x]() {
return x;
};
}
This is dangerous because x no longer exists after the function returns.
Safer version:
auto makeLambda() {
x = ;
[x]() {
x;
};
}
Comparisons
Lambda vs other ways to provide behavior
| Approach | Best for | Can use local state easily? | Boilerplate | Reusability |
|---|---|---|---|---|
| Regular function | Reusable logic | No | Low | High |
| Function pointer | Simple callback APIs | No | Low | Medium |
| Functor class | Stateful reusable callable objects | Yes | High | High |
| Lambda | Short local behavior | Yes | Low | Medium |
Lambda vs named function
| Feature |
|---|
Cheat Sheet
Quick syntax
[]() { }
[](int x) { return x * 2; }
[x]() { return x; }
[&x]() { x++; }
[=]() { /* capture used variables by value */ }
[&]() { /* capture used variables by reference */ }
[x]() mutable { x++; }
Parts of a lambda
[capture](parameters) -> return_type {
// body
}
capture: outer variables to bring inparameters: inputs to the lambdareturn_type: optional if C++ can infer itbody: code to run
Common uses
std::sortstd::find_ifstd::count_ifstd::for_eachstd::transform- callbacks
- threads
Capture rules
[x]copies
FAQ
What is a lambda expression in C++?
A lambda expression is an anonymous callable object that lets you write function-like logic inline, often right where it is passed to an algorithm or callback.
Why use lambdas instead of regular functions in C++?
Use lambdas when the logic is short, local, and needs access to nearby variables. Regular functions are better for reusable logic that deserves a name.
What does the capture list mean in a C++ lambda?
The capture list controls which outside variables the lambda can use, and whether it gets them by value or by reference.
When should I capture by value vs by reference?
Capture by value when you want a safe copy or snapshot. Capture by reference when the lambda must modify the original variable or avoid copying, but be careful with lifetimes.
Are lambdas faster than normal functions in C++?
Not necessarily, but they are often efficient and can be inlined by the compiler. The main benefit is usually cleaner and more expressive code.
Can a C++ lambda return a value?
Yes. A lambda can return values just like a normal function, and the return type is often inferred automatically.
Can I store a lambda in a variable?
Yes. You can usually store it with auto, or in std::function when you need a common callable wrapper.
When should I avoid using lambdas?
Avoid them when the logic is long, reused in many places, or needs a clear descriptive name.
Mini Project
Description
Build a small C++ program that manages a list of numbers and uses lambda expressions to process them. This project demonstrates the most common beginner use cases for lambdas: filtering, counting, and sorting with local rules.
Goal
Create a program that counts numbers above a threshold, removes unwanted values, and sorts the remaining values using lambda expressions.
Requirements
- Create a
std::vector<int>with at least 8 numbers. - Use a lambda with
std::count_ifto count how many values are greater than a chosen threshold. - Use a lambda with
std::remove_ifto remove values below a minimum allowed value. - Use a lambda with
std::sortto sort the remaining values in descending order. - Print the results after each step.
Keep learning
Related questions
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Casts Explained: C-Style Cast vs static_cast vs dynamic_cast
Learn the difference between C-style casts, static_cast, and dynamic_cast in C++ with clear examples, safety rules, and real usage tips.
C++ Value Categories Explained: lvalues, rvalues, xvalues, glvalues, and prvalues
Learn C++ value categories clearly: lvalues, rvalues, xvalues, glvalues, and prvalues, with examples and why C++11 introduced them.