STL Algorithms

The <algorithm> and <numeric> headers turn dozens of loops you'd otherwise write by hand — sorting, searching, transforming, summing — into single, well-tested, often faster calls that work uniformly across every STL container.

Almost every hand-written loop over a container is really an instance of one of a small number of recurring patterns: find something, count something, transform every element, remove some elements, sum everything up. The STL captures these patterns as generic algorithms — free functions in <algorithm> and <numeric> that operate entirely through iterators, so they work identically on a vector, a deque, or a raw array, and are customizable through predicates and comparators (often supplied as lambdas). Writing std::sort(v.begin(), v.end()) instead of hand-rolling quicksort isn't just shorter — it's also very likely faster and definitely more correct, since these algorithms are written and tuned by people who specialize in exactly this. This page walks through the algorithms you'll reach for constantly: sorting, searching, copying and transforming, removing, finding extremes, accumulating, and testing predicates across a range.

Sorting: sort and stable_sort

std::sort(first, last) rearranges the range [first, last) into ascending order using operator< by default, and typically runs in O(n log n) using an introsort (a hybrid of quicksort, heapsort, and insertion sort). It requires random-access iterators, so it works on vector, deque, and array, but not on list (which provides its own member function, list::sort, instead).

The first sort uses the default operator<, producing ascending order: 1 2 3 5 8 9. The second call passes std::greater<int>() as an explicit comparator, reversing the order to 9 8 5 3 2 1 — any callable taking two elements and returning a bool for 'should the first come before the second' works here, including a lambda.

std::sort does not guarantee that elements comparing equal keep their original relative order (it isn't stable). std::stable_sort(first, last) makes that guarantee, at the cost of typically being somewhat slower and, in the worst case, needing extra memory. Reach for stable_sort whenever you're sorting by one key but need ties to preserve an earlier ordering — for example, sorting students by grade after they were already sorted alphabetically by name.

#include <algorithm>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> nums{5, 2, 8, 1, 9, 3};
    std::sort(nums.begin(), nums.end());

    for (int n : nums)
        std::cout << n << " ";
    std::cout << "\n";

    std::sort(nums.begin(), nums.end(), std::greater<int>());
    for (int n : nums)
        std::cout << n << " ";
    std::cout << "\n";

    return 0;
}

Searching: find, find_if, count, count_if

std::find(first, last, value) does a linear scan and returns an iterator to the first element equal to value, or last if none matches — it works on any forward iterator, so it has no requirement that the range be sorted. std::find_if(first, last, predicate) generalizes this to any custom condition expressed as a predicate (a callable returning bool).

find scans until it finds a 9, at index 3. find_if scans for the first element satisfying the lambda [](int n){ return n % 2 == 0; } — the first even number, 2, at index 2. count_if doesn't stop early; it scans the entire range and returns how many elements satisfy the predicate — 3 (2, 4, and 6).

std::count(first, last, value) is to std::find what count_if is to find_if: it counts exact matches to a value instead of stopping at the first one. All four of these algorithms are O(n) — a linear scan is unavoidable when the range isn't known to be sorted.

#include <algorithm>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> nums{3, 7, 2, 9, 4, 6};

    auto it = std::find(nums.begin(), nums.end(), 9);
    if (it != nums.end())
        std::cout << "found 9 at index " << (it - nums.begin()) << "\n";

    auto firstEven = std::find_if(nums.begin(), nums.end(),
                                   [](int n) { return n % 2 == 0; });
    if (firstEven != nums.end())
        std::cout << "first even: " << *firstEven << "\n";

    int evenCount = std::count_if(nums.begin(), nums.end(),
                                   [](int n) { return n % 2 == 0; });
    std::cout << "even count: " << evenCount << "\n";

    return 0;
}

Binary Search on Sorted Ranges

Once a range is sorted, searching no longer needs a linear scan. std::binary_search(first, last, value) returns just true or false in O(log n). std::lower_bound(first, last, value) returns an iterator to the first element not less than value (the earliest valid insertion point that keeps the range sorted), and std::upper_bound returns an iterator to the first element strictly greater than value. std::equal_range returns both bounds as a pair in one call, delimiting every element equal to value.

binary_search(nums, 5) returns true since 5 is present. lower_bound finds the first 3 (index 1), and upper_bound finds the first element after all the 3s (index 4, which is 5) — subtracting the two iterators gives 3, the count of how many 3s are in the range, without ever calling std::count.

#include <algorithm>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> nums{1, 3, 3, 3, 5, 7, 9};

    bool found = std::binary_search(nums.begin(), nums.end(), 5);
    std::cout << std::boolalpha << found << "\n";

    auto lo = std::lower_bound(nums.begin(), nums.end(), 3);
    auto hi = std::upper_bound(nums.begin(), nums.end(), 3);
    std::cout << "3 occupies indices [" << (lo - nums.begin())
              << ", " << (hi - nums.begin()) << ")\n";
    std::cout << "count of 3: " << (hi - lo) << "\n";

    return 0;
}

Copying, Moving, and Transforming

std::copy(first, last, dest) copies each element from the source range to an output iterator, one at a time, without modifying the source. std::move(first, last, dest) does the same but moves each element instead of copying it, leaving the source elements in a valid-but-unspecified state — use it when the source range's contents are no longer needed afterward, to avoid a needless deep copy.

dest must already have room for source.size() elements before copy runs — copy overwrites existing elements at dest, it doesn't grow the container (use std::back_inserter(dest) if you want it to append instead). transform applies the lambda to each element of source and writes the result into the corresponding position of doubled, producing {2, 4, 6, 8, 10}.

#include <algorithm>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> source{1, 2, 3, 4, 5};
    std::vector<int> dest(source.size());

    std::copy(source.begin(), source.end(), dest.begin());

    std::vector<int> doubled(source.size());
    std::transform(source.begin(), source.end(), doubled.begin(),
                    [](int n) { return n * 2; });

    for (int n : doubled)
        std::cout << n << " ";
    std::cout << "\n";

    return 0;
}

Removing and Reordering: remove, remove_if, reverse, rotate

std::remove and std::remove_if don't actually shrink a container — algorithms in <algorithm> never resize containers, since they only work through iterators and don't know the container's type. Instead, remove_if shifts every element that should be kept toward the front of the range and returns an iterator marking the new logical end; everything from there to the original end is left in an unspecified state and must be erased separately, which is why this is always paired with a container's erase member in what's called the erase-remove idiom.

remove_if compacts every odd element to the front, in order, and returns newEnd marking where the kept elements stop. nums.erase(newEnd, nums.end()) then actually deletes the now-meaningless trailing elements and shrinks the vector's size. The result is {1, 3, 5, 7} — this two-step erase-remove pattern is the idiomatic way to delete matching elements from a vector.

std::reverse(first, last) reverses a range in place. std::rotate(first, middle, last) rotates a range left so that the element at middle becomes the new first element, wrapping the elements that were before it around to the end — useful for cyclic shifts without manually computing indices.

#include <algorithm>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> nums{1, 2, 3, 4, 5, 6, 7, 8};

    auto newEnd = std::remove_if(nums.begin(), nums.end(),
                                  [](int n) { return n % 2 == 0; });
    nums.erase(newEnd, nums.end());

    for (int n : nums)
        std::cout << n << " ";
    std::cout << "\n";

    return 0;
}

Min, Max, and Extremes

std::min(a, b) and std::max(a, b) compare two values directly and return the smaller or larger one. std::min_element(first, last) and std::max_element(first, last) scan a whole range and return an iterator to the smallest or largest element — an iterator, not the value itself, so you dereference it to get the value.

min_element and max_element each do a single O(n) pass over nums. Because they return iterators rather than values, you get both the value (via *minIt) and its position (via subtracting nums.begin()) from one call, which a value-returning std::min/std::max over the whole range could not give you.

#include <algorithm>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> nums{4, 8, 1, 9, 3};

    auto minIt = std::min_element(nums.begin(), nums.end());
    auto maxIt = std::max_element(nums.begin(), nums.end());

    std::cout << "min: " << *minIt << " at index " << (minIt - nums.begin()) << "\n";
    std::cout << "max: " << *maxIt << " at index " << (maxIt - nums.begin()) << "\n";

    return 0;
}

Numeric Algorithms: accumulate and reduce

std::accumulate(first, last, init), declared in <numeric>, folds a range down to a single value by repeatedly applying an operation (addition by default) starting from init — it's the generic version of the running-total loop you'd otherwise write by hand.

The first call uses the default operation (addition), folding 0 + 1 + 2 + 3 + 4 + 5 into 15. The second call replaces addition with a lambda that multiplies, starting from 1 instead of 0 (the correct identity element for multiplication), producing 1*1*2*3*4*5 = 120.

std::reduce (C++17), also in <numeric>, computes the same kind of fold as accumulate but does not guarantee left-to-right evaluation order, which allows an implementation to run it in parallel or reorder operations for speed. Use reduce instead of accumulate when the operation is associative and commutative (like ordinary addition of numbers) and you don't need a strictly ordered fold — reduce can be meaningfully faster on large ranges, especially combined with a parallel execution policy.

#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);
    int product = std::accumulate(nums.begin(), nums.end(), 1,
                                   [](int a, int b) { return a * b; });

    std::cout << "sum: " << sum << ", product: " << product << "\n";

    return 0;
}

Predicates Over a Range: all_of, any_of, none_of

std::all_of, std::any_of, and std::none_of each test a predicate against every element of a range and return a single bool, short-circuiting as soon as the answer is determined: all_of returns false the moment one element fails, any_of returns true the moment one element passes, and none_of returns false the moment one element passes.

allEven is true (every element is even). anyOverFive is true (6, 8, and 10 are all over 5, and any_of stops as soon as it finds the first one, 6). noneNegative is true (nothing in nums is negative). This prints true true true.

These three algorithms replace the pattern of writing a hand-rolled loop with an early-return flag variable — bool allEven = true; for (...) if (...) { allEven = false; break; } — with a single expressive call whose name states the intent directly.

Why does std::remove_if not shrink the vector it operates on, and what is the erase-remove idiom?

remove_if only has iterators to work with, not the container itself, so it has no way to call a resizing operation like erase — it can only rearrange elements within [first, last) and return an iterator marking where the kept elements end. The erase-remove idiom pairs remove_if's return value with the container's own erase member function — v.erase(std::remove_if(v.begin(), v.end(), pred), v.end()) — so the container is actually shortened afterward.

What is the difference between std::find and std::binary_search, and why can't binary_search be used on an arbitrary unsorted range?

std::find performs a linear O(n) scan and works on any range, sorted or not. std::binary_search assumes the range is already sorted and repeatedly halves the search space, achieving O(log n) — but that halving logic only produces correct results because sortedness guarantees which half a target value could possibly be in; on unsorted data that guarantee doesn't hold, so binary_search can return an incorrect answer without any error or warning.

What's the practical difference between std::accumulate and std::reduce, and when would you prefer reduce?

accumulate guarantees strict left-to-right evaluation order, applying the operation to elements in sequence exactly as written. reduce makes no such ordering guarantee, which permits an implementation to evaluate the fold out of order or in parallel. Prefer reduce for large ranges when the operation is associative and commutative (like plain numeric addition), since it can be faster; prefer accumulate whenever the operation's result depends on evaluation order (like floating-point sums where order affects rounding, or non-commutative operations).

Why do std::min_element and std::max_element return iterators rather than values directly?

Returning an iterator lets the caller both read the extreme value (by dereferencing) and know its position within the range (by comparing or subtracting against begin()), which a value-only return couldn't provide. It also lets the caller modify the found element in place if the iterator isn't const, or erase it, using the same iterator that located it.

How do std::all_of, std::any_of, and std::none_of differ in what they return, and how does each one short-circuit?

all_of returns true only if every element satisfies the predicate, and it can short-circuit to false the instant one element fails. any_of returns true if at least one element satisfies the predicate, short-circuiting to true the instant one element passes. none_of returns true only if no element satisfies the predicate, short-circuiting to false the instant one element passes — it's logically the negation of any_of.

Write a C++ program that sorts a std::vector<std::string> by string length (shortest first) using std::sort with a custom comparator lambda.

The lambda replaces the default operator< comparison with a length-based comparison, so std::sort reorders words by size rather than lexicographically. The result is fig, kiwi, pear, apple, banana (lengths 3, 4, 4, 5, 6) — kiwi and pear may print in either relative order since std::sort is not guaranteed stable for equal-length ties.

Write a C++ program that uses the erase-remove idiom to remove all negative numbers from a std::vector<int>.

remove_if moves every non-negative element to the front of the range, in order, and returns an iterator marking where the kept elements end; erase then removes everything from that iterator to the actual end. The result is {3, 7, 2, 9} — all negative values gone, and the vector's size reduced to match.

What does this code print?

It prints 115. accumulate starts from the given initial value (100 here, not 0) and adds each element of v in order: 100 + 1 + 2 + 3 + 4 + 5 = 115. The initial value parameter matters both for the starting point of the fold and, implicitly, for the result's type.

Write a C++ program that uses std::lower_bound to find the correct insertion position for a new value into an already-sorted std::vector<int>, keeping it sorted, without calling std::sort again afterward.

lower_bound finds the first element not less than 6, which is 7 (at index 3), and returns an iterator to it. Inserting 6 at that position keeps the vector sorted without needing a full re-sort: the result is {1, 3, 5, 6, 7, 9}. This pattern — lower_bound plus insert — is the standard way to maintain a sorted vector one insertion at a time.

#include <algorithm>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> nums{2, 4, 6, 8, 10};

    bool allEven = std::all_of(nums.begin(), nums.end(),
                                [](int n) { return n % 2 == 0; });
    bool anyOverFive = std::any_of(nums.begin(), nums.end(),
                                    [](int n) { return n > 5; });
    bool noneNegative = std::none_of(nums.begin(), nums.end(),
                                      [](int n) { return n < 0; });

    std::cout << std::boolalpha << allEven << " " << anyOverFive << " " << noneNegative << "\n";

    return 0;
}
std::vector<int> v{1, 2, 3, 4, 5};
int total = std::accumulate(v.begin(), v.end(), 100);
std::cout << total << "\n";