Lambda Expressions

A lambda is a tiny function you write right where you use it — no separate declaration, no name needed. They make standard algorithms like std::sort and std::count_if read like plain English, and they're the everyday glue of modern C++. By the end of this lesson you'll write lambdas with parameters and captures, and pass them to algorithms to filter, sort, and accumulate data.

Learn Lambda Expressions in our free C++ course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick recall.

Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

A lambda is a sticky note with an instruction that you hand to a helper. When you ask std::sort to put a list in order, you can attach a sticky note that says "shorter words first" — that note is the lambda. The capture list is what you write on the back of the note from your own desk: copy a number to use ([value]), or point back at the original so changes are reflected ([&reference]). The helper doesn't need to know your whole program — just the small rule on the note.

1. Your First Lambdas

The simplest lambda has an empty capture list [] , no parameters, and a body. Store it in a variable with auto (each lambda has its own unique compiler-generated type) and call it like a function. Lambdas can take parameters and return values just like named functions — the return type is usually deduced automatically.

2. Captures: by Value vs by Reference

A lambda can use variables from the surrounding scope, but only if you capture them. [factor] takes a copy — a snapshot frozen at the moment the lambda is created. [&total] takes a reference to the original, so the lambda can read and modify the live value. Choosing the right kind of capture is the heart of using lambdas well.

Your turn. Fill in the capture so the lambda can use the bonus variable by value:

These lines define a lambda that doubles a number and use it. Put them in the correct order:

Open main (C), define the twice lambda (B), call it — printing 42 (A), then return 0; (D) and close the brace (E). A lambda must be defined before it's called.

3. Lambdas with std::sort

The most common use of a lambda is as a comparator for std::sort . You pass a lambda taking two elements that returns true when the first should come before the second. This lets you sort by any rule you like — by length, by a struct field, descending, whatever — without writing a separate function.

4. count_if, for_each, find_if

Lambdas shine with the standard algorithms. A lambda returning bool is called a predicate : count_if counts how many elements satisfy it, find_if returns the first that does. for_each runs an action on every element — capture a variable by reference to accumulate a result.

5. mutable: Lambdas That Remember

By default, variables captured by value are const inside the lambda — the body can read its copies but not change them. Add the mutable keyword after the parameter list to allow changes to those copies. Combined with an init-capture like [count = 0] , this lets a lambda keep its own state between calls, behaving like a tiny stateful object.

A lambda is syntactic sugar for an unnamed function object (a "functor"): the compiler generates a hidden struct with an operator() , and the captures become its data members. That's why each lambda has a unique type and why auto is the natural way to hold one.

When you need a single named type that can hold any callable with a given signature — for example to store different lambdas in one container — use std::function , at a small runtime cost:

Predict the output before revealing the answer.

6 — k was captured by value (a copy of 5). Changing k to 100 afterward doesn't affect the lambda's copy: 1 + 5 = 6.

7 — total is captured by reference, so both calls modify the original: 0 + 3 + 4 = 7.

012 — post-increment returns the old value then increments the captured copy: 0, then 1, then 2.

Combine a predicate and a reference capture: count the expensive items and sum only those. The comment outline keeps you on track.

Practice quiz

What is the basic syntax of a C++ lambda expression?

A lambda is written [capture](parameters) { body }, with an optional -> returnType after the parameters.

What does the capture [x] do?

  • Captures x by reference
  • Captures x by value (a copy)
  • Captures everything by value
  • Captures nothing

Answer: Captures x by value (a copy). [x] captures x by value — the lambda gets its own copy, frozen at creation time. [&x] would capture by reference.

What prints? int k = 5; auto f = [k](int n){ return n + k; }; k = 100; cout << f(1);

  • 101
  • 6
  • 105
  • 1

Answer: 6. k was captured by value (a copy of 5). Changing k to 100 afterward does not affect the copy, so f(1) = 1 + 5 = 6.

What is the final value of total? int total = 0; auto add = [&total](int n){ total += n; }; add(3); add(4);

  • 0
  • 3
  • 7
  • 12

Answer: 7. total is captured by reference, so both calls modify the original: 0 + 3 + 4 = 7.

By default, can a lambda modify a variable it captured by value inside its body?

  • Yes, always
  • No — the copy is const unless you add mutable
  • Only if it is an int

Answer: No — the copy is const unless you add mutable. Value captures are const inside the body by default. Adding mutable after the parameter list lets the lambda change its own copies.

Which keyword is the natural way to store a lambda in a variable?

  • auto
  • lambda
  • var
  • void

Answer: auto. Each lambda has its own unique compiler-generated type, so auto is the natural way to hold one.

A lambda passed to std::sort as a comparator should return true when:

  • The two elements are equal
  • The first element should come before the second
  • The first element is greater
  • Sorting is finished

Answer: The first element should come before the second. A sort comparator must be a strict less-than: return true when 'a' should come before 'b' (e.g. a.size() < b.size()).

What does std::count_if(begin, end, pred) return?

  • An iterator to the first match
  • true or false
  • How many elements satisfy the predicate
  • The sum of matching elements

Answer: How many elements satisfy the predicate. count_if counts how many elements satisfy the predicate. find_if returns an iterator to the first match; for_each runs an action on each.

What is a lambda really, under the hood?

  • A macro
  • An unnamed function object (functor) with operator() and captures as data members
  • A plain function pointer
  • A preprocessor directive

Answer: An unnamed function object (functor) with operator() and captures as data members. The compiler generates a hidden struct with operator(); the captures become its data members — which is why each lambda has a unique type.

To store any callable with signature int(int) in one named type, you would use:

  • std::function<int(int)>
  • auto
  • int*
  • std::vector<int>

Answer: std::function<int(int)>. std::function<int(int)> can hold any callable matching that signature (at a small runtime cost), letting you reassign different lambdas.