Skip to content
C++
Language
since C++11
Basic

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.

cpp
#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

cpp
#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.

cpp
#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.

cpp
#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.

cpp
#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.

cpp
#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

cpp
#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.

cpp
// 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

cpp
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.

cpp
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:

cpp
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 behavior

Store indices rather than iterators when you need to reference elements across a potential reallocation.

Quick Reference

AlgorithmHeaderWhat 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_heap and std::partial_sort.