STL Algorithm Mastery

Reviewed & published by Brayan K

By the end of this lesson you'll replace hand-written loops with the C++ Standard Library's <algorithm> and <numeric> tools — sorting with your own rules, searching, summing, transforming, and safely deleting elements — using one line where you used to write fifteen.

Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

Think of STL algorithms as power tools. You could cut every plank with a hand saw (your own for loop), but a builder reaches for the right power tool: a circular saw to cut (sort), a stud finder to locate (find), a tape measure to total up (accumulate). Each tool clamps onto a stretch of work — that's the iterator range, from begin() to end(). You supply the rule (a lambda: "cut here", "keep this one") and the tool does the repetitive work, faster and with fewer mistakes than doing it by hand.

1. Sort, Find & Count

std::sort rearranges a range in place — ascending by default. To sort by your rule, pass a third argument: a comparator. A comparator is a lambda that takes two elements and returns a bool answering one question — "should a come before b?" Return a > b and you've flipped it to descending. find hunts for an exact value and hands back an iterator (compare it to end() to check for a miss), while count and count_if tally matches. Read this worked example, run it, then you'll write your own.

#include <iostream>
#include <vector>
#include <algorithm>   // sort, find, count, count_if, min/max_element
using namespace std;

int main() {
    vector<int> nums = {42, 17, 8, 95, 23, 61, 3};

    // sort — rearranges the range in place, ascending by default.
    // You pass a RANGE: begin() ... end() (end is one-past-the-last).
    sort(nums.begin(), nums.end());
    cout << "Ascending:  ";
    for (int n : nums) cout << n << " ";   // 3 8 17 23 42 61 95
    cout << endl;

    // Custom comparator — a lambda that returns "should a come before b?".
    // Returning a > b sorts descending.
    sort(nums.begin(), nums.end(), [](int a, int b) {
        return a > b;                      // biggest first
    });
    cout << "Descending: ";
    for (int n : nums) cout << n << " ";   // 95 61 42 23 17 8 3
    cout << endl;

    // find — looks for an exact value, returns an iterator (or end()).
    auto it = find(nums.begin(), nums.end(), 23);
    if (it != nums.end())
        cout << "Found 23 at index "
             << distance(nums.begin(), it) << endl;   // index 3

    // count — how many elements equal a value.
    cout << "How many 42s? " << count(nums.begin(), nums.end(), 42) << endl; // 1

    // count_if — how many satisfy a predicate (a lambda returning bool).
    int evens = count_if(nums.begin(), nums.end(),
                         [](int n) { return n % 2 == 0; });
    cout << "Even numbers: " << evens << endl;          // 3  (42, 8, ...)
    return 0;
}

// ⚠️ No expected-output panel for this one, on purpose.
// These numbers are stopwatch readings. They depend on the machine, the
// compiler and what else it is doing, so only the direction of the
// comparison is the lesson — never the figures.
//
// One real run on the machine that builds this site printed:
//    Ascending:  3 8 17 23 42 61 95
//    Descending: 95 61 42 23 17 8 3
//    Found 23 at index 3
//    How many 42s? 1
//    Even numbers: 2

Your turn. The program below is almost complete — fill in the two blanks marked ___ using the hints, then run it and check it against the expected output.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    // 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
    vector<int> scores = {72, 48, 91, 63, 30, 88, 55};

    // 1) Sort scores HIGHEST first using a comparator lambda.
    //    A comparator returns "should a come before b?".
    sort(scores.begin(), scores.end(), [](int a, int b) {
        return ___;          // 👉 a > b   (so bigger values come first)
    });

    cout << "Ranked: ";
    for (int s : scores) cout << s << " ";
    cout << endl;

    // 2) Count how many scores are a PASS (60 or more) with count_if.
    int passes = count_if(scores.begin(), scores.end(), [](int s) {
        return ___;          // 👉 s >= 60
    });
    cout << "Passes: " << passes << endl;

    // ✅ Expected output:
    //    Ranked: 91 88 72 63 55 48 30
    //    Passes: 4
    return 0;
}

2. Accumulate, Transform & for_each

std::accumulate (from <numeric>, not <algorithm>) folds a whole range into a single value — its third argument is both the starting total and the type of the result. std::transform applies a function to every element and writes the results into another range. std::for_each runs an action on each element without producing a new range. And min_element / max_element return an iterator to the smallest or largest value — remember to dereference it with * to read the value out.

#include <iostream>
#include <vector>
#include <algorithm>   // transform, for_each, min_element, max_element
#include <numeric>     // accumulate  (lives here, NOT in <algorithm>)
using namespace std;

int main() {
    vector<int> prices = {1299, 2499, 599, 3999, 899};   // cents

    // accumulate — fold a whole range into ONE value.
    // The 3rd argument (0) is the starting total AND fixes the type.
    int totalCents = accumulate(prices.begin(), prices.end(), 0);
    cout << "Total cents: " << totalCents << endl;        // 9295

    // transform — apply a function to every element, write into a new range.
    // Here: cents -> dollars. dollars must already be the right size.
    vector<double> dollars(prices.size());
    transform(prices.begin(), prices.end(), dollars.begin(),
              [](int cents) { return cents / 100.0; });
    cout << "In dollars: ";
    for (double d : dollars) cout << "$" << d << " ";     // $12.99 $24.99 ...
    cout << endl;

    // min_element / max_element — return an ITERATOR to the smallest/largest.
    // Dereference with * to read the value.
    auto cheapest  = min_element(prices.begin(), prices.end());
    auto priciest  = max_element(prices.begin(), prices.end());
    cout << "Cheapest: "  << *cheapest  << "c"  << endl;  // 599c
    cout << "Priciest: "  << *priciest  << "c"  << endl;  // 3999c

    // for_each — run an action on every element (no new range produced).
    cout << "All prices: ";
    for_each(prices.begin(), prices.end(), [](int c) {
        cout << c << "c ";
    });
    cout << endl;
    return 0;
}

// ✅ Expected output:
//    Total cents: 9295
//    In dollars: $12.99 $24.99 $5.99 $39.99 $8.99
//    Cheapest: 599c
//    Priciest: 3999c
//    All prices: 1299c 2499c 599c 3999c 899c

Now you try. Total a list of workout minutes, then convert each one to hours. Fill in the two blanks — watch the type on the accumulate seed and the division:

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

int main() {
    // 🎯 YOUR TURN — fill in the blanks, then run.
    vector<int> minutes = {30, 45, 15, 60, 20};   // workout lengths

    // 1) Add them all up with accumulate. Start the total at 0.
    int totalMins = accumulate(minutes.begin(), minutes.end(), ___);  // 👉 0
    cout << "Total minutes: " << totalMins << endl;

    // 2) Convert each value to HOURS (a double) using transform.
    //    Divide by 60.0 so you keep the decimals.
    vector<double> hours(minutes.size());
    transform(minutes.begin(), minutes.end(), hours.begin(),
              [](int m) { return ___; });        // 👉 m / 60.0

    cout << "In hours: ";
    for (double h : hours) cout << h << " ";
    cout << endl;

    // ✅ Expected output:
    //    Total minutes: 170
    //    In hours: 0.5 0.75 0.25 1 0.333333
    return 0;
}

3. Deleting Elements: the Erase-Remove Idiom

This is the one that surprises everybody. std::remove and std::remove_if do not actually delete anything. An algorithm only sees a pair of iterators — it has no power to resize the container. So remove just shuffles the elements you're keeping to the front and returns an iterator to the new logical end; the leftover slots are still there. To truly shrink the container you pass that iterator to the container's own erase() method. Doing both together is the famous erase-remove idiom.

#include <iostream>
#include <vector>
#include <algorithm>   // remove, remove_if
using namespace std;

void printVec(const string& label, const vector<int>& v) {
    cout << label << ": ";
    for (int n : v) cout << n << " ";
    cout << endl;
}

int main() {
    // remove() does NOT delete anything. It shifts the elements you keep
    // to the front and returns an iterator to the new "logical end".
    // Everything after that point is leftover junk — still in the vector!
    vector<int> data = {1, 2, 3, 2, 4, 2, 5};
    auto newEnd = remove(data.begin(), data.end(), 2);   // removes the 2s
    printVec("After remove (NOT erased yet)", data);      // 1 3 4 5 ? ? ?
    cout << "Size is still " << data.size() << "!" << endl;  // 7

    // erase() is the container's OWN method — it actually shrinks the vector.
    // Pass it the iterator remove() returned, up to the real end().
    data.erase(newEnd, data.end());
    printVec("After erase (the erase-remove idiom)", data);  // 1 3 4 5
    cout << "Size now " << data.size() << endl;              // 4

    // The idiom in one line: remove_if + erase.
    // remove_if drops every element whose predicate returns true.
    vector<int> scores = {85, 42, 93, 67, 51, 28, 76};
    scores.erase(
        remove_if(scores.begin(), scores.end(),
                  [](int s) { return s < 60; }),   // drop fails (< 60)
        scores.end()
    );
    printVec("Passing scores (>= 60)", scores);    // 85 93 67 76
    return 0;
}

// ✅ Expected output:
//    After remove (NOT erased yet): 1 3 4 5 4 2 5
//    Size is still 7!
//    After erase (the erase-remove idiom): 1 3 4 5
//    Size now 4
//    Passing scores (>= 60): 85 93 67 76

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

TaskCodeReturns
Sort ascendingsort(v.begin(), v.end())void (in place)
Sort by rulesort(b, e, [](int a, int b){ return a>b; })void (in place)
Find a valuefind(v.begin(), v.end(), 42)iterator / end()
Count matchescount_if(b, e, pred)how many
Sum a rangeaccumulate(b, e, 0)the total
Map each elementtransform(b, e, out, fn)writes to out
Largest value*max_element(b, e)the value
Delete matchesv.erase(remove_if(b, e, pred), e)shrinks v

Mini-Challenge: Temperature Report

No blanks this time — just a brief and an outline to keep you on track. Combine sort, min_element/max_element, accumulate, and count_if into one small report. Build it, run it, and check your output against the example in the comments.

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

int main() {
    // 🎯 MINI-CHALLENGE: Temperature report
    vector<int> temps = {18, 25, 31, 14, 22, 29, 16};

    // 1. Sort temps from COLDEST to hottest (a comparator lambda, or plain sort).
    // 2. Use min_element / max_element to print the low and the high.
    //    Remember they return iterators — dereference with * to read the value.
    // 3. Use accumulate to get the total, then divide by temps.size() for the
    //    average (cast to double so you keep decimals).
    // 4. Use count_if to count how many days were "warm" (>= 25).
    //
    // ✅ Example output:
    //    Low: 14  High: 31
    //    Average: 22.1429
    //    Warm days (>= 25): 3

    // your code here
    return 0;
}

🎉 Lesson Complete

Practice quiz

Which header is std::sort, std::find, and std::count_if declared in?

  • <numeric>
  • <vector>
  • <algorithm>
  • <functional>

Answer: <algorithm>. sort, find, count_if, transform, for_each, remove_if and min/max_element all live in <algorithm>.

Which header does std::accumulate live in?

  • <numeric>
  • <algorithm>
  • <vector>
  • <cmath>

Answer: <numeric>. accumulate lives in <numeric>, not <algorithm> — a common include mistake.

What should a comparator lambda passed to std::sort return?

  • The larger of the two elements
  • An int: -1, 0, or 1
  • The sorted range
  • A bool answering 'should a come before b?'

Answer: A bool answering 'should a come before b?'. A comparator returns a bool — true when a should sort before b. Use a < b for ascending, a > b for descending.

To sort a vector in DESCENDING order, the comparator should return:

  • a < b
  • a > b
  • a <= b
  • a == b

Answer: a > b. Returning a > b means bigger elements come first, giving descending order.

What does std::find return?

  • An iterator to the element, or end() if not found
  • The matching value, or 0 if not found
  • The index of the element
  • A bool

Answer: An iterator to the element, or end() if not found. find returns an iterator; compare it to end() to detect a miss.

What is the result of accumulate(v.begin(), v.end(), 0) for v = {1299, 2499, 599, 3999, 899}?

  • 5
  • 3999
  • 9295
  • 0

Answer: 9295. accumulate folds the range starting from the seed 0: 1299+2499+599+3999+899 = 9295.

Why does the seed type in accumulate matter, e.g. 0 vs 0.0?

  • It controls the iteration order
  • It fixes the result type — 0 sums as int and truncates decimals, 0.0 sums as double
  • 0.0 is invalid for accumulate
  • It changes which header is needed

Answer: It fixes the result type — 0 sums as int and truncates decimals, 0.0 sums as double. The seed sets the accumulation type; seeding with int 0 truncates decimals, while 0.0 keeps them.

What does std::transform do?

  • Sorts a range in place
  • Removes elements that match a predicate
  • Counts matching elements
  • Applies a function to every element and writes results into another range

Answer: Applies a function to every element and writes results into another range. transform maps each input element through a function into an output range.

What do min_element and max_element return?

  • The smallest/largest value directly
  • An iterator to the smallest/largest element — dereference with * to read the value
  • An index
  • A sorted copy of the range

Answer: An iterator to the smallest/largest element — dereference with * to read the value. They return iterators; you write *max_element(v.begin(), v.end()) to get the value.

Why must remove_if be paired with the container's erase() (the erase-remove idiom)?

  • erase is faster than remove_if
  • remove_if deletes the wrong elements without erase
  • remove_if only shifts kept elements forward and returns the new logical end; it can't resize the container
  • erase sorts the range first

Answer: remove_if only shifts kept elements forward and returns the new logical end; it can't resize the container. Algorithms see only iterators, so remove_if can't shrink the container — erase() does the actual shrinking.

Continue this course

Frequently asked questions

Do I always need to include <algorithm> and <numeric>?

Yes. sort, find, count_if, transform, for_each, remove_if, min_element and max_element all live in <algorithm>. accumulate lives in <numeric>. If you forget the include you'll get an 'X was not declared in this scope' error.

Why does erasing after remove_if look so awkward?

remove_if only shuffles the kept elements to the front and returns an iterator to the new logical end — it cannot resize the container because algorithms only see iterators, not the container itself. You pass that returned iterator to the container's own erase() to actually shrink it. This two-step combo is the 'erase-remove idiom'.

What does a comparator lambda return?

A bool. It answers one question: 'should a come before b?' Return true when a should sort earlier. For ascending order return a < b; for descending return a > b. It must NOT return true for equal values, or you break sort's rules.

What's the difference between find and find_if?

find looks for a specific value (find(v.begin(), v.end(), 42)). find_if looks for the first element that makes a predicate return true (find_if(v.begin(), v.end(), [](int n){ return n > 50; })). Both return end() when nothing matches.

Why does accumulate start with a value like 0?

That third argument is the starting total (the 'seed') and it also fixes the result type. accumulate(v.begin(), v.end(), 0) sums into an int; accumulate(v.begin(), v.end(), 0.0) sums into a double. Seeding with the wrong type silently truncates your decimals.

Related lessons