Master STL Algorithms: Write Less, Do More
Apply STL algorithms to replace manual loops with expressive, bug-resistant operations on any container in your C++ code.
By the end of this page, you will know how to search, sort, transform, and fold data using the C++ standard library's algorithm functions β and you will understand why each one exists, what it returns, and how to avoid the mistakes that trip up nearly every developer the first time.
What and Why
The C++ standard library ships with roughly 100 algorithm functions spread across <algorithm> and <numeric>. Each one operates on a range β a pair of iterators that mark the beginning and one-past-the-end of some sequence of elements. Because they work through iterators, they are container-agnostic: the same std::sort call works on a vector, an array, or a deque.
Three reasons to reach for them instead of writing a raw loop:
- Safety. The algorithm handles edge cases (empty range, single element) correctly and consistently.
- Expressiveness.
std::sort(v.begin(), v.end())announces intent in a way a four-line loop never can. - Performance. Standard library implementations are heavily optimized. Under C++17, many algorithms accept an execution policy that enables automatic parallelism.
All examples on this page require C++11 for lambda support. Compile with -std=c++11 or later.
Step by Step
Searching: std::find and std::find_if
The simplest algorithm: locate an element and hand back an iterator to it.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6};
// Search by value
auto it = std::find(nums.begin(), nums.end(), 5);
if (it != nums.end()) {
std::cout << "Found 5 at index "
<< std::distance(nums.begin(), it) << '\n';
}
// Search by predicate β first value greater than 4
auto big = std::find_if(nums.begin(), nums.end(),
[](int x) { return x > 4; });
if (big != nums.end()) {
std::cout << "First value > 4: " << *big << '\n';
}
}Every algorithm returns something meaningful. std::find returns an iterator to the matching element, or .end() when nothing matches. Always compare against .end() before dereferencing.
Sorting: std::sort
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6};
std::sort(nums.begin(), nums.end()); // ascending
for (int n : nums) std::cout << n << ' '; // 1 1 2 3 4 5 6 9
std::cout << '\n';
// Supply a comparator for descending order
std::sort(nums.begin(), nums.end(), [](int a, int b) { return a > b; });
for (int n : nums) std::cout << n << ' '; // 9 6 5 4 3 2 1 1
std::cout << '\n';
}Transforming: std::transform
std::transform applies a function to each element and writes results into an output range. The output can be the same range (in-place) or a separate container β but you must ensure the destination has enough space before writing.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
std::vector<int> squares(nums.size()); // pre-size the destination
std::transform(nums.begin(), nums.end(), squares.begin(),
[](int x) { return x * x; });
for (int s : squares) std::cout << s << ' '; // 1 4 9 16 25
std::cout << '\n';
}Folding: std::accumulate
std::accumulate collapses a range into a single value by repeatedly applying a binary operation. The third argument is the initial value β it sets the starting point for the fold.
#include <numeric>
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
int sum = std::accumulate(nums.begin(), nums.end(), 0);
std::cout << "Sum: " << sum << '\n'; // 15
// Custom operation: product. Initial value must be 1, not 0.
int product = std::accumulate(nums.begin(), nums.end(), 1,
[](int acc, int x) { return acc * x; });
std::cout << "Product: " << product << '\n'; // 120
}Common Patterns
Filter and erase: the erase-remove idiom
Removing elements that satisfy a condition is a two-step dance. std::remove_if shuffles matching elements to the back and returns an iterator to the new logical end. Then .erase() trims the container.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8};
nums.erase(
std::remove_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; }),
nums.end());
for (int n : nums) std::cout << n << ' '; // 1 3 5 7
std::cout << '\n';
}Transform then accumulate
A productive pipeline: derive a scalar metric from a collection of objects without an intermediate container.
#include <numeric>
#include <vector>
#include <string>
#include <iostream>
int main() {
std::vector<std::string> words = {"hello", "world", "cpp"};
int total = std::accumulate(words.begin(), words.end(), 0,
[](int acc, const std::string& w) {
return acc + static_cast<int>(w.size());
});
std::cout << "Total chars: " << total << '\n'; // 13
}Count with a predicate: std::count_if
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int evens = std::count_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; });
std::cout << evens << " even numbers\n"; // 5 even numbers
}What Can Go Wrong
Forgetting .erase() after std::remove_if
std::remove_if does not resize the container β it only knows about iterators, not the container itself. The elements past the returned iterator are left in a valid-but-unspecified state, and .size() remains unchanged.
// WRONG β discards the return value; nothing is actually removed
std::remove_if(nums.begin(), nums.end(), pred);
// CORRECT β erase the tail the algorithm left behind
nums.erase(std::remove_if(nums.begin(), nums.end(), pred), nums.end());Wrong initial value in std::accumulate
std::vector<int> nums = {2, 3, 4};
// WRONG β initial value 0 means the product is always 0
int bad = std::accumulate(nums.begin(), nums.end(), 0,
[](int a, int b) { return a * b; });
// CORRECT β 1 is the identity element for multiplication
int good = std::accumulate(nums.begin(), nums.end(), 1,
[](int a, int b) { return a * b; });Passing an unsorted range to a binary search algorithm
std::binary_search, std::lower_bound, and std::upper_bound require the range to already be sorted. An unsorted range produces undefined behavior β no crash, just silently wrong results.
std::vector<int> v = {5, 3, 1, 4, 2};
// WRONG β undefined behavior; range is not sorted
bool found = std::binary_search(v.begin(), v.end(), 3);
// CORRECT β sort first
std::sort(v.begin(), v.end());
bool found_ok = std::binary_search(v.begin(), v.end(), 3);Iterator invalidation
Never use an iterator after an operation that could reallocate the container:
std::vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4); // may reallocate β 'it' is now dangling
std::cout << *it; // undefined behaviorStore indices rather than iterators when you need to reference elements across a potential reallocation.
Quick Reference
| Algorithm | Header | What it does |
|---|---|---|
std::find(b, e, val) | <algorithm> | Iterator to first element equal to val |
std::find_if(b, e, pred) | <algorithm> | Iterator to first element matching predicate |
std::count_if(b, e, pred) | <algorithm> | Count of elements matching predicate |
std::sort(b, e) | <algorithm> | Sort range ascending; optional comparator |
std::stable_sort(b, e) | <algorithm> | Sort, preserving order of equal elements |
std::transform(b, e, out, fn) | <algorithm> | Apply fn element-wise, write to out |
std::remove_if(b, e, pred) | <algorithm> | Shuffle matches to end; return new logical end |
std::copy_if(b, e, out, pred) | <algorithm> | Copy matching elements to out |
std::accumulate(b, e, init) | <numeric> | Fold with + or custom binary op |
std::binary_search(b, e, val) | <algorithm> | true if val exists in sorted range |
std::min_element(b, e) | <algorithm> | Iterator to smallest element |
std::max_element(b, e) | <algorithm> | Iterator to largest element |
What's Next
With these building blocks in hand, you are ready for more specialized territory:
- Fold operations β
std::reduce,std::inclusive_scan, and parallel-friendly folding from C++17. - Modifying algorithms β
std::fill,std::generate,std::replace_if, and other in-place mutation tools. - Min/max algorithms β
std::clamp,std::minmax_element, and reading results cleanly with structured bindings. - Merge algorithms β combining two sorted ranges without a temporary container.
- Heap operations β building priority queues on the fly and performing partial sorts with
std::make_heapandstd::partial_sort.