Lambda Functions vs Traditional Functions in C++

Written by: Nurul Hasan
For: Fellow C++ learners and curious minds
Special thanks to: ChatGPT, for helping untangle the curly braces
Modern C++ (C++11 and onward) introduced lambda expressions, also known as anonymous functions. They’re powerful, flexible, and used everywhere — including competitive programming and system-level code.
But for many developers, the syntax can be confusing — especially when you see something like:
function<void(TreeNode*)> dfs = [&](TreeNode* node) { ... };
Let’s unpack everything with a clear comparison to traditional functions.
1. Traditional Functions
A traditional function in C++ looks like this:
void sayHello() {
cout << "Hello!" << endl;
}
And a recursive one:
void dfs(TreeNode* node) {
if (!node) return;
dfs(node->left);
cout << node->val << " ";
dfs(node->right);
}
Defined globally or inside classes.
Can call themselves recursively without anything extra.
Don’t have access to local variables in
main()
or other functions unless passed as parameters or made global.
2. Lambda Functions
A lambda is an anonymous function (i.e., it doesn't have a name) that you can define inline, even inside other functions.
Syntax:
[capture](parameter_list) -> return_type {
// body
};
Example with all parts:
auto add = [](int a, int b) -> int {
return a + b;
};
Let’s break it down:
[]
— Capture clause: lets the lambda "see" outside variables(int a, int b)
— Parameters-> int
— Return type (optional if obvious){ ... }
— Function body
Recursive Lambda Syntax
std::function<return_type(parameter_list)> func_name = [&](parameter_list) -> return_type {
// base case(s)
// recursive call: func_name(arguments)
};
Recursive Lambda example
std::function<int(int)> factorial = [&](int n) -> int {
if (n <= 1) return 1;
return n * factorial(n - 1);
};
Example: Basic Lambda
int x = 10;
auto lambda = [x]() -> void { // we could omit `-> void`
cout << x << endl; // x is captured by value
};
lambda(); // prints 10
Capturing Variables
Lambdas cannot access variables outside themselves unless you tell them to.
1. By Value [=]
:
int a = 5;
auto f = [=]() { cout << a; }; // captured by value (read-only)
2. By Reference [&]
:
int a = 5;
auto f = [&]() { a = 10; }; // captured by reference (modifiable)
3. Mixed:
auto f = [=, &b]() { /* a by value, b by reference */ };
Recursion in Lambdas
Lambdas cannot refer to themselves inside their body unless you assign them to a named function-like variable (typically std::function
).
✅ Traditional (works automatically):
void dfs(TreeNode* node) {
dfs(node->left);
}
❌ This fails:
auto dfs = [](TreeNode* node) {
dfs(node->left); // ❌ undefined inside itself!
};
✅ Proper Recursive Lambda (using std::function
):
function<void(TreeNode*)> dfs = [&](TreeNode* node) {
if (!node) return;
dfs(node->left);
cout << node->val << " ";
dfs(node->right);
};
Here’s what’s going on:
function<void(TreeNode*)>
: declares a variabledfs
that can hold any function matching that signature.&
: captures all outer variables by reference (includingdfs
itself).Inside the lambda,
dfs(node->left)
works becausedfs
is defined before the body.
Full Working Example: Count Nodes in a Tree
Traditional:
int countNodes(TreeNode* root) {
if (!root) return 0;
return 1 + countNodes(root->left) + countNodes(root->right);
}
Recursive Lambda Version:
function<int(TreeNode*)> count = [&](TreeNode* node) -> int {
if (!node) return 0;
return 1 + count(node->left) + count(node->right);
};
int total = count(root);
Traditional vs Lambda Summary
Feature | Traditional Function | Lambda Function |
Has a name | Yes (dfs , countNodes ) | No (anonymous, unless assigned to a variable) |
Inline definition | ❌ No | ✅ Yes |
Access outer vars | ❌ Must pass manually | ✅ Use [&] , [=] to capture |
Can be recursive | ✅ Yes | ✅ Yes, but needs std::function wrapper |
Use case | Utility, class/global methods | Short functions, closures, local logic |
When Should You Use Lambdas?
When you want a quick, inline function inside
main()
or any other function.When you want to avoid polluting global scope with many named functions.
When you need access to outer variables without explicitly passing them.
When using STL algorithms like
sort
,for_each
,accumulate
, etc.
Conclusion
Lambdas bring more flexibility, especially in local and functional-style programming. They're also great when used with recursive DFS, backtracking, and STL algorithms.
Traditional functions are still great for global, recursive, and reusable utilities.
Thank you for reading through this article. I'm currently learning and exploring some of these concepts myself, and while going through them, I thought it might be helpful to write things down in a way that’s easy to revisit and understand. If it helped you too, I’m really glad.
That’s all — just wanted to share what I’m learning. Thanks again for taking the time to read ❤️.
Subscribe to my newsletter
Read articles from Nurul Hasan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
