STL Algorithms Overview
Complete guide to <algorithm> and std::ranges algorithms — sorting, searching, transforming, partitioning, and parallel execution.
STL Algorithmssince C++98Generic functions in <algorithm> that operate on iterator-delimited half-open ranges to sort, search, transform, and partition sequences — decoupled from any specific container type.
Overview
The <algorithm> header exposes over 100 algorithms built on one abstraction: a half-open range [first, last) described by two iterators. The algorithm is indifferent to what those iterators point into — std::vector, a raw array, a std::deque, or a custom container — which is the source of their reusability.
C++20 introduced two upgrades. First, std::ranges::* counterparts that accept range objects directly, eliminating the repetitive .begin()/.end() boilerplate. Second, projections — a callable applied to each element before comparison — letting you sort by a member field without writing a comparator.
// C++98/11: iterator-pair form
std::sort(v.begin(), v.end());
std::sort(v.begin(), v.end(), std::greater<int>{});
// C++20: ranges form — accepts the container directly
std::ranges::sort(v);
std::ranges::sort(v, std::greater{});
// C++20: projection — sort by member without a lambda
std::ranges::sort(people, {}, &Person::age);Execution policies (C++17, <execution>) let the classic std::* iterator-pair algorithms run in parallel or with SIMD vectorisation. The std::ranges::* algorithms do not accept execution policies as of C++23; fall back to the std::* iterator-pair forms when you need std::execution::par.
std::sort has been guaranteed O(n log n) worst-case since C++11 (implementations use introsort — quicksort with a heapsort fallback). Prior to C++11, only average-case O(n log n) was required.
Sorting
std::ranges::sort(v); // C++20
std::ranges::sort(v, std::greater{}); // descending
std::ranges::sort(v, {}, &S::field); // by member projection — C++20
std::ranges::stable_sort(v); // preserves relative order of equals
// partial_sort: only first n positions are fully sorted — O(n log k)
std::ranges::partial_sort(v, v.begin() + 5);
// nth_element: O(n) average — element at pos as if sorted; others are partitioned
std::ranges::nth_element(v, v.begin() + v.size() / 2); // median pivot
bool ok = std::ranges::is_sorted(v);
auto it = std::ranges::is_sorted_until(v); // iterator to first out-of-order elementSearching
// Linear search — O(n)
auto it = std::ranges::find(v, 42);
auto it2 = std::ranges::find_if(v, [](int n){ return n > 10; });
auto it3 = std::ranges::find_if_not(v, pred);
// Binary search — O(log n), range MUST be sorted
bool found = std::ranges::binary_search(v, 42);
auto lo = std::ranges::lower_bound(v, 42); // first element >= 42
auto hi = std::ranges::upper_bound(v, 42); // first element > 42
auto [lo2, hi2] = std::ranges::equal_range(v, 42); // subrange where value == 42
// Subsequence search
auto r = std::ranges::search(haystack, needle); // first occurrence of needle
auto e = std::ranges::find_end(haystack, needle); // last occurrence
std::ranges::search_n(v, 3, 0); // 3 consecutive zeros
// Min / max
auto mn_it = std::ranges::min_element(v);
auto mx_it = std::ranges::max_element(v);
auto [mn, mx] = std::ranges::minmax(v); // returns min_max_result — C++20
std::ranges::clamp(val, lo_val, hi_val); // std::clamp since C++17Predicates
std::ranges::all_of(v, pred);
std::ranges::any_of(v, pred);
std::ranges::none_of(v, pred);
std::ranges::count(v, val);
std::ranges::count_if(v, pred);
std::ranges::contains(v, val); // C++23 only — use find != end in C++20std::ranges::contains requires C++23. In C++20, write std::ranges::find(v, val) != std::ranges::end(v).
Transforming
// copy
std::ranges::copy(src, std::back_inserter(dst));
std::ranges::copy_if(src, std::back_inserter(dst), pred);
std::ranges::copy_n(src.begin(), 5, dst.begin());
std::ranges::copy_backward(src, dst_end);
// transform (map)
std::ranges::transform(v, dst.begin(), [](int n){ return n * 2; });
std::ranges::transform(a, b, dst.begin(), std::plus{}); // binary form
// generate / fill
std::mt19937 rng{std::random_device{}()};
std::uniform_int_distribution<int> dist(0, 100);
std::ranges::generate(v, [&]{ return dist(rng); });
std::ranges::fill(v, 0);
std::ranges::fill_n(v.begin(), 5, -1);
// replace
std::ranges::replace(v, 0, -1);
std::ranges::replace_if(v, pred, -1);
std::ranges::replace_copy(src, dst.begin(), 0, -1);
// reverse / rotate
std::ranges::reverse(v);
std::ranges::rotate(v, v.begin() + 3); // rotate left by 3 positions
std::ranges::rotate_copy(v, v.begin() + 3, dst.begin());Removing
std::ranges::remove shifts surviving elements to the front and returns a subrange representing the dead tail [new_end, old_end). It does not resize the container — you must call erase separately.
// Correct: use the returned subrange to erase the tail
auto tail = std::ranges::remove(v, 0); // C++20 — returns subrange
v.erase(tail.begin(), tail.end());
// C++20 one-liners — strongly prefer for vector/deque/string/list
std::erase(v, 0); // C++20
std::erase_if(v, [](int n){ return n < 0; }); // C++20
// Deduplicate (sort first — unique only removes consecutive duplicates)
std::ranges::sort(v);
auto dup_tail = std::ranges::unique(v); // C++20 — returns subrange
v.erase(dup_tail.begin(), dup_tail.end());Partitioning
std::ranges::partition(v, pred); // relative order not preserved
std::ranges::stable_partition(v, pred); // preserves relative order — O(n log n)
bool ok = std::ranges::is_partitioned(v, pred);
auto mid = std::ranges::partition_point(v, pred); // O(log n) on partitioned rangeSet Operations (sorted ranges)
All set algorithms require both input ranges to be sorted under the same comparator. Violating this precondition is undefined behaviour.
std::ranges::merge(a, b, out_it);
std::ranges::set_union(a, b, out_it);
std::ranges::set_intersection(a, b, out_it);
std::ranges::set_difference(a, b, out_it);
std::ranges::set_symmetric_difference(a, b, out_it);
std::ranges::inplace_merge(v, mid);
bool subset = std::ranges::includes(a, b); // true if every element of b is in aHeap Operations
std::ranges::make_heap(v); // O(n) — establishes max-heap invariant
std::ranges::push_heap(v); // insert v.back() into heap — O(log n)
std::ranges::pop_heap(v); // moves max to v.back(), re-heapifies — O(log n)
std::ranges::sort_heap(v); // O(n log n) — heap must be valid first
bool h = std::ranges::is_heap(v);Permutations
std::ranges::next_permutation(v); // C++20 — advances to next lexicographic permutation
std::ranges::prev_permutation(v);
std::ranges::shuffle(v, rng); // Fisher-Yates; std::shuffle since C++11, ranges since C++20
std::ranges::sample(src, out, n, rng); // reservoir sample; std::sample since C++17, ranges since C++20
std::ranges::is_permutation(a, b);std::random_shuffle was deprecated in C++14 and removed in C++17. Always use std::shuffle (C++11) or std::ranges::shuffle (C++20) with an explicit URBG.
Parallel Execution (C++17)
Only the classic std::* iterator-pair algorithms accept execution policies. std::ranges::* algorithms have no parallel overloads as of C++23.
#include <execution>
std::sort(std::execution::par, v.begin(), v.end());
std::transform(std::execution::par_unseq,
v.begin(), v.end(), dst.begin(), fn);
// Execution policies (C++17 unless noted):
// seq — sequential; identical to omitting a policy
// par — parallel threads
// par_unseq — parallel + SIMD; callable must not lock or allocate
// unseq — SIMD only, single thread (C++20)par_unseq permits the runtime to interleave SIMD lanes across threads. Any callable passed under this policy must be free of locks, memory allocation, and non-vectorisable control flow.
Best Practices
Prefer std::ranges::* in C++20 codebases. Projections replace single-field lambdas, accepting a range object removes iterator boilerplate, and the result types carry richer information (e.g., in_out_result, min_max_result). Fall back to the std::* iterator-pair forms only when you need parallel execution.
Use std::erase / std::erase_if over the erase-remove idiom (C++20+). The two-step pattern is error-prone and the C++20 free functions are available for vector, deque, string, list, forward_list, and the associative containers.
Exploit projections to avoid temporary transforms.
// Unnecessary allocation:
std::vector<std::string> names;
std::ranges::transform(people, std::back_inserter(names), &Person::name);
std::ranges::sort(names);
// Sort the original by field directly — no allocation:
std::ranges::sort(people, {}, &Person::name);Know the complexity contracts. partial_sort is O(n log k) where k is the sorted prefix length — much cheaper than full sort when k ≪ n. nth_element is O(n) average with introselect implementations guaranteeing O(n) worst-case. stable_sort degrades to O(n log² n) when it cannot obtain extra memory.
Common Pitfalls
Calling binary search algorithms on unsorted ranges. binary_search, lower_bound, upper_bound, equal_range, and all set operations have a sorted-input precondition. They will silently produce wrong results — not a crash or an exception.
Forgetting erase after remove or unique. The algorithm only reorders elements; size() and end() are unchanged until you explicitly erase the tail.
// Bug: v retains its original size; the tail holds garbage
std::ranges::remove(v, 0);
// Correct
v.erase(std::ranges::remove(v, 0).begin(), v.end());
// Or prefer the C++20 one-liner:
std::erase(v, 0);Using std::ranges::contains in a C++20 project. It is a C++23 addition. Compilers targeting C++20 will reject it; use std::ranges::find(v, val) != std::ranges::end(v).
Calling std::sort on std::list. std::sort requires random-access iterators; std::list provides only bidirectional iterators. The code will not compile. Use list.sort(), which is a member function.
Using par_unseq with stateful callables. Any mutex, allocation, or non-vectorisable branch inside the callable can deadlock or corrupt state. Under par_unseq the implementation may execute multiple loop iterations in a single thread via SIMD before switching threads.
See Also
std::rangesadaptors — lazy, composable views over ranges<numeric>—std::accumulate,std::reduce(C++17 parallel),std::iota,std::partial_sum<execution>— execution policy types and requirements- Iterators — iterator categories, concepts, and the requirements each algorithm imposes