STL Overview in C++: Containers, Iterators and Algorithms with Runnable Examples

Build a practical STL mental model, then trace seven scores through sorting, searching, filtering and aggregation. The same data clarifies map, set and priority_queue choices.

KnowledgeGate Team

Exam prep & CS education

Updated 2 Aug 20268 min read

STL can first look like a bag of unrelated names: vector, map, begin() and sort(). Memorising each name separately makes even short programs confusing because it hides how the pieces cooperate. One mental model carries all of it: a container owns data, iterators identify a range, and an algorithm performs an operation on that range. Seven exam scores, 72, 88, 72, 95, 64, 88 and 81, exercise the whole model: one vector holds them, one sort orders them, one lower_bound finds the first score of at least 80, one predicate drops everything under 70, and one accumulate totals what remains. STL is not a separate language, and not every standard-library facility belongs to it: std::string and the iostreams ship with the standard library but sit outside the container, iterator and algorithm core.

1. What STL contains and how its parts fit together

Containers store values, iterators move through them, and algorithms such as sort, find, count and remove_if accept iterator ranges. Function objects and lambdas supply policies, such as [](int score) { return score < 70; } for selecting low scores.

Most STL ranges are half-open: [first, last). first is included, while last points just after the final element and must never be dereferenced. In vector<int> v{10, 20, 30, 40}; sort(v.begin() + 1, v.end());, the range covers 20, 30, 40; 10 remains outside it.

Part

Job

Running example

vector<int>

Own values

Store seven scores

begin() and end()

Delimit a range

Cover all seven scores

sort

Reorder a range

Put scores in ascending order

Lambda

State a policy

Select scores below 70

That three-part split is not unique to C++. Java collections and Python's standard library also separate storage, traversal and operation in the same way, which is why the model is worth learning once and reusing. The Coding and DSA courses cover the C, C++, Java and Python versions of it.

2. Choosing an STL container before writing the algorithm

Choose by the problem, not familiarity. Sequence containers are vector, deque, list, forward_list and array. Ordered associative containers include set, multiset, map and multimap; unordered choices include unordered_set and unordered_map. Adapters such as stack, queue and priority_queue deliberately expose a restricted interface instead of general iterator ranges.

Need

Choice

Useful property

Indexed access and appending

vector

Constant-time indexing; amortised constant-time push_back

Key-value data in key order

map

Logarithmic lookup and insertion

Key-value data without order

unordered_map

Average constant-time operations, possible linear worst cases

Unique ordered values

set

Uniqueness plus ordering

Retrieve the current largest first

priority_queue

Direct access to the maximum

For {72, 88, 72, 95, 64, 88, 81}, a vector preserves the full input. A map<int, int> can count each score, a set<int> keeps unique sorted values, and a priority_queue<int> retrieves the maximum first. Using a set for the original input would silently discard the second 72 and second 88.

3. Runnable STL example: sort, search, filter and aggregate seven scores

This program compiles as C++17. <algorithm> supplies sort, lower_bound, remove_if and count; <numeric> supplies accumulate; and <iomanip> supplies setprecision for the two-decimal average.

#include <algorithm>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <vector>

int main() {
    std::vector<int> scores{72, 88, 72, 95, 64, 88, 81};

    std::sort(scores.begin(), scores.end());
    std::cout << "sorted:";
    for (int score : scores) std::cout << ' ' << score;
    std::cout << '\n';

    auto first80 = std::lower_bound(scores.begin(), scores.end(), 80);
    auto first80Index = first80 - scores.begin();
    std::cout << "first score >= 80: " << *first80
              << " at index " << first80Index << '\n';

    auto newEnd = std::remove_if(scores.begin(), scores.end(),
                                 [](int score) { return score < 70; });
    scores.erase(newEnd, scores.end());
    std::cout << "after removing scores < 70:";
    for (int score : scores) std::cout << ' ' << score;
    std::cout << '\n';

    int count88 = std::count(scores.begin(), scores.end(), 88);
    int total = std::accumulate(scores.begin(), scores.end(), 0);
    double average = static_cast<double>(total) / scores.size();

    std::cout << "count of 88: " << count88 << '\n';
    std::cout << "total: " << total << ", average: "
              << std::fixed << std::setprecision(2) << average << '\n';
}

Sorting produces [64, 72, 72, 81, 88, 88, 95]. Since that range is ordered, lower_bound(..., 80) points to 81 at zero-based index 3. The predicate removes only 64, leaving [72, 72, 81, 88, 88, 95]. Counting 88 gives 2. The sum is 72 + 72 + 81 + 88 + 88 + 95 = 496; dividing by 6 gives 82.666..., printed to two decimal places as 82.67.

sorted: 64 72 72 81 88 88 95
first score >= 80: 81 at index 3
after removing scores < 70: 72 72 81 88 88 95
count of 88: 2
total: 496, average: 82.67

std::sort is required to do O(n log n) comparisons, and typical implementations use an introsort hybrid rather than one textbook algorithm. The comparison of sorting algorithms and their complexity works through where those bounds come from and which algorithms stay stable.

Flow diagram of the seven scores as boxed cells: unsorted, then sorted with lower_bound(80) marked at index 3, then 64 crossed out, leaving six scores summing to 496, average 82.67.

4. Iterators, erase-remove and invalidation rules

Iterator category controls which algorithms are legal. A vector has random-access iterators, so std::sort works. A list has bidirectional iterators, making std::sort(list.begin(), list.end()) ill-formed; use list.sort(). A priority_queue exposes no iterators, only adapter operations such as top, push and pop.

remove_if moves the retained elements to the front and returns a new logical end. It does not reduce scores.size(): the elements from newEnd to the old end() are still valid objects, but their values are unspecified because they have been moved from. erase(newEnd, scores.end()) performs the physical size change from 7 to 6.

A vector reallocation invalidates all iterators, pointers and references. Erasure invalidates iterators at and after the erased position. In map and set, insertions and erasures leave iterators to other elements valid, but the erased element's iterator becomes invalid. The program therefore calculates first80Index before erase. It must not dereference first80 afterwards without reacquiring it.

5. Extend the same data with map, set and priority_queue

Rebuild the original seven-score vector, and three containers answer three different questions about it: how often each score occurs, which scores are distinct, and which score is highest.

#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <vector>

int main() {
    std::vector<int> scores{72, 88, 72, 95, 64, 88, 81};

    std::map<int, int> frequency;
    for (int score : scores) ++frequency[score];
    for (const auto& entry : frequency)
        std::cout << entry.first << " -> " << entry.second << '\n';

    std::set<int> distinct(scores.begin(), scores.end());
    std::cout << "distinct values: " << distinct.size() << '\n';

    std::priority_queue<int> pending(scores.begin(), scores.end());
    std::cout << "top three:";
    for (int i = 0; i < 3; ++i) {
        std::cout << ' ' << pending.top();
        pending.pop();
    }
    std::cout << '\n';
}
64 -> 1
72 -> 2
81 -> 1
88 -> 2
95 -> 1
distinct values: 5
top three: 95 88 88

Ordered map iteration walks the keys ascending, so it prints 64 -> 1, 72 -> 2, 81 -> 1, 88 -> 2, 95 -> 1. An unordered_map can count too, but its iteration order is not promised.

std::set<int> distinct(scores.begin(), scores.end()); produces [64, 72, 81, 88, 95]. Five elements are correct only when uniqueness is intended, because the duplicate 72 and the duplicate 88 are gone. Both map and set are ordered search structures, so lookup, insertion and erasure are logarithmic rather than constant, although the standard fixes the complexity bounds and leaves the tree implementation open.

With std::priority_queue<int> pending(scores.begin(), scores.end());, three top() and pop() calls yield 95, 88, 88. Duplicates remain. The adapter exposes the highest-priority element, not a sorted iterable view.

Diagram splitting the seven input scores three ways: a map of counts 64:1, 72:2, 81:1, 88:2, 95:1, a set of five unique values, and a priority_queue popping 95, 88, 88.

6. STL mistakes that make correct-looking code fail

Dereferencing scores.end() is undefined behaviour. Calling lower_bound on the original unsorted sequence has no meaningful sorted-search answer because its ordering precondition is missing. A wrong iterator pair can exclude required values or form an invalid range.

Map lookup can mutate data. Given std::map<std::string, int> attempts{{"Asha", 2}};, attempts["Kabir"] returns 0, inserts "Kabir", and increases the size to 2. Use find() for a read-only C++17 check. contains() requires C++20.

Three quick repairs are worth remembering:

  • If unordered_map output appears in an unexpected order, sort the keys or use map.

  • If remove_if leaves the physical size unchanged, complete the erase-remove idiom.

  • If a header uses using namespace std;, remove it and qualify names instead, because every file that includes that header inherits the same name collisions.

7. How coding tests and interviews assess STL choices

Test and interview questions on STL usually turn on a single decision: which container the requirement forces. Indexed scores that keep duplicates call for vector. Unique sorted scores call for set. Frequency by sorted score calls for map, while frequency with fast average lookup and no ordering requirement calls for unordered_map. Repeated retrieval of the current highest score calls for priority_queue. A complete answer says what happens to duplicates, whether ordering is promised, what the complexity is, and which iterators survive the mutation.

Predict before running: lower_bound(80) returns 81 at index 3 after sorting. Erase-remove leaves size 6 and retains both 88s. A set built from the seven original values has size 5.

For practice, create vector<vector<int>> adjacency(4) with undirected edges 0-1, 0-2 and 1-3. A queue-based BFS from 0, visiting neighbours in stored order, yields 0, 1, 2, 3, and std::queue is the adapter that keeps that loop to a few lines. Graph algorithms: BFS, DFS and Dijkstra traced step by step carries the same traversal further, and the C++ track of Coding For Placements has the practice problems.

8. STL in C++: the short version and next step

Remember four rules:

  1. Choose a container by ownership, ordering and duplicate requirements.

  2. Use iterators to describe a valid half-open range.

  3. Check an algorithm's iterator and ordering preconditions.

  4. Re-check iterator validity after every mutation.

The correctness check is compact: seven inputs sort to [64, 72, 72, 81, 88, 88, 95]; filtering removes 64, and the remaining six total 496 with average 82.67.

The C++ track of the C++ Programming course covers the language from the first program upward, which is the ground the STL sits on. Before moving on, rerun the program with a cutoff of 85 instead of 70. Predict [88, 88, 95], total 271 and average 90.33, then compile it and check.