Lambda Functions vs Traditional Functions in C++

Nurul HasanNurul Hasan
4 min read

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

  • -> intReturn 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 variable dfs that can hold any function matching that signature.

  • &: captures all outer variables by reference (including dfs itself).

  • Inside the lambda, dfs(node->left) works because dfs 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

FeatureTraditional FunctionLambda Function
Has a nameYes (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 caseUtility, class/global methodsShort 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 ❤️.

0
Subscribe to my newsletter

Read articles from Nurul Hasan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nurul Hasan
Nurul Hasan