C++ STL: Containers, Algorithms, and Iterators
The C++ Standard Template Library (STL) is a collection of generic containers, algorithms, and iterators. It is the standard library’s most influential part — designed by Alexander Stepanov in the 1990s, it separates data structures from algorithms using iterators as a glue layer. Understanding the STL is essential for writing idiomatic, efficient C++.
This guide covers the most commonly used STL components with practical examples.
Containers
Sequence Containers
#include <vector>
#include <deque>
#include <list>
#include <array>
#include <forward_list>
// vector — dynamic array, contiguous memory
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6); // O(1) amortized
vec.pop_back(); // O(1)
vec.insert(vec.begin() + 2, 99); // O(n)
vec.erase(vec.begin()); // O(n)
vec[3] = 42; // O(1) random access
// deque — double-ended queue
std::deque<int> deq = {1, 2, 3};
deq.push_front(0); // O(1)
deq.push_back(4); // O(1)
deq.pop_front(); // O(1)
// list — doubly-linked list
std::list<int> lst = {1, 2, 3};
lst.push_front(0);
lst.push_back(4);
lst.insert(++lst.begin(), 99); // O(1) at iterator position
// array — fixed-size, stack-allocated
std::array<int, 5> arr = {10, 20, 30, 40, 50};
// arr.size() == 5, no dynamic allocation
Associative Containers
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
// set — ordered, unique keys (usually red-black tree)
std::set<int> s = {3, 1, 4, 1, 5, 9}; // {1, 3, 4, 5, 9}
s.insert(2);
s.erase(1);
bool exists = s.contains(4); // C++20
// map — ordered key-value pairs
std::map<std::string, int> ages;
ages["Alice"] = 30;
ages["Bob"] = 25;
// ages is ordered alphabetically by key
// unordered_set — hash table, O(1) average
std::unordered_set<int> us = {3, 1, 4, 1, 5};
// unordered_map — hash table key-value
std::unordered_map<std::string, int> scores;
scores["player1"] = 100;
scores["player2"] = 85;Container Adaptors
#include <stack>
#include <queue>
// stack — LIFO
std::stack<int> st;
st.push(1);
st.push(2);
st.top(); // 2
st.pop(); // removes 2
// queue — FIFO
std::queue<int> q;
q.push(1);
q.push(2);
q.front(); // 1
q.back(); // 2
q.pop(); // removes 1
// priority_queue — max-heap by default
std::priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(5);
pq.top(); // 5
Iterators
Iterators bridge containers and algorithms. They come in different categories:
#include <iterator>
std::vector<int> vec = {10, 20, 30, 40, 50};
// Basic iteration
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << ' ';
---
// Range-based for (uses iterators internally)
for (const auto& v : vec) {
std::cout << v << ' ';
---
// Iterator categories
auto it = vec.begin();
++it; // Forward iterator (all containers)
--it; // Bidirectional (list, set, map)
it += 3; // Random access (vector, deque, array)
auto diff = it - vec.begin(); // Distance (random access)
// Reverse iteration
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
std::cout << *it << ' '; // 50, 40, 30, 20, 10
---
// Iterator utilities
std::advance(it, 2); // Advance by n
auto dist = std::distance(vec.begin(), it); // Distance between iterators
Algorithms
#include <algorithm>
#include <numeric>
std::vector<int> v = {5, 2, 8, 1, 9, 3, 7, 4, 6};
// Sorting
std::sort(v.begin(), v.end()); // {1, 2, 3, 4, 5, 6, 7, 8, 9}
std::partial_sort(v.begin(), v.begin() + 3, v.end()); // Top 3 sorted
// Searching
auto it = std::find(v.begin(), v.end(), 5);
bool exists = std::binary_search(v.begin(), v.end(), 5); // Requires sorted range
// Modifying
std::fill(v.begin(), v.end(), 0); // Set all to 0
std::transform(v.begin(), v.end(), v.begin(),
[](int x) { return x * 2; }); // Double each element
// Removing
auto new_end = std::remove(v.begin(), v.end(), 0);
v.erase(new_end, v.end()); // Erase-remove idiom
// Min/Max
auto [min_it, max_it] = std::minmax_element(v.begin(), v.end());
// Counting
int count = std::count(v.begin(), v.end(), 5);
// Numeric algorithms (in <numeric>)
int sum = std::accumulate(v.begin(), v.end(), 0);
int product = std::accumulate(v.begin(), v.end(), 1, std::multiplies<>());
std::vector<int> diffs;
std::adjacent_difference(v.begin(), v.end(), std::back_inserter(diffs));Common Patterns
Erase-Remove Idiom
// Remove all elements matching a value
std::vector<int> vec = {1, 2, 3, 2, 4, 2, 5};
vec.erase(
std::remove(vec.begin(), vec.end(), 2),
vec.end()
);
// vec == {1, 3, 4, 5}
// Remove elements satisfying a predicate
vec.erase(
std::remove_if(vec.begin(), vec.end(),
[](int x) { return x % 2 == 0; }),
vec.end()
);Custom Comparators
struct Person {
std::string name;
int age;
---;
// Sort by age descending, then name ascending
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) {
if (a.age != b.age) return a.age > b.age;
return a.name < b.name;
});
// Custom set with comparator
auto cmp = [](const Person& a, const Person& b) {
return a.name < b.name;
---;
std::set<Person, decltype(cmp)> person_set(cmp);Efficient Insertion
std::map<std::string, int> scores;
// Insert only if key doesn't exist
auto [it, inserted] = scores.try_emplace("Alice", 100);
if (inserted) {
std::cout << "Inserted new entry\n";
---
// Get or insert default
int& score = scores["Bob"]; // Creates with 0 if not exists
// Insert with hint (for ordered containers)
auto hint = scores.lower_bound("Charlie");
scores.emplace_hint(hint, "Charlie", 50);Performance Considerations
| Container | Insert | Erase | Find | Random Access | |
Container Selection Guide
Choosing the right container is critical for performance. Each STL container makes different trade-offs:
| Container | Insert | Erase | Find | Ordering | Iterator invalidation | |
FAQ
What is the difference between malloc and new in C++? malloc allocates raw memory without calling constructors; new allocates memory and calls the constructor. In C++, prefer new for objects. free vs delete follows the same pattern — delete calls the destructor.
How do I prevent memory leaks in C/C++? Use RAII (Resource Acquisition Is Initialization) in C++ — smart pointers like std::unique_ptr and std::shared_ptr automatically free memory. In C, always pair every malloc with a free and use tools like Valgrind or AddressSanitizer to detect leaks.
What is undefined behavior in C/C++? Undefined behavior occurs when code performs operations that the language standard does not define — dereferencing a null pointer, buffer overflow, signed integer overflow. The compiler may generate any code, including unexpected results or crashes.
Should I learn C or C++ first? Learn C first if you want to understand low-level memory and system programming. Learn C++ first if you want object-oriented features and the STL. Both are valuable; C++ builds on C concepts.
What is the difference between a header file and a source file? Header files (.h) declare interfaces — function prototypes, class definitions, macros. Source files (.c or .cpp) implement the declarations. Headers are #included; source files are compiled separately and linked.
———–|——–|——-|——|———-|———————-|
| vector | O(1) at end | O(1) at end | O(n) | Insertion order | On reallocation |
| deque | O(1) at ends | O(1) at ends | O(n) | Insertion order | Never invalidates elements |
| list | O(1) anywhere | O(1) anywhere | O(n) | Insertion order | Never invalidates |
| set/map | O(log n) | O(log n) | O(log n) | Sorted | Never invalidates |
| unordered_set/unordered_map | O(1) avg | O(1) avg | O(1) avg | None | On rehash |
Iterators and Ranges
STL iterators provide a uniform interface for traversing containers. C++20 ranges simplify iteration further:
std::vector<int> v = {1, 2, 3, 4, 5};
// Traditional iterators
for (auto it = v.begin(); it != v.end(); ++it) { }
// Range-based for loop
for (int x : v) { }
// C++20 ranges
auto even = v | std::views::filter([](int x) { return x % 2 == 0; });
for (int x : even) { }Algorithms Library
The <algorithm> header provides generic algorithms that operate on iterators. These are more expressive and less error-prone than hand-written loops:
std::vector<int> data = {5, 2, 8, 1, 9, 3};
std::sort(data.begin(), data.end()); // Sort
auto it = std::lower_bound(data.begin(), // Binary search
data.end(), 5);
std::transform(data.begin(), data.end(), // Apply function
data.begin(), [](int x) { return x * 2; });Allocators
Allocators manage memory for containers. The default std::allocator uses new and delete. Custom allocators enable pool allocation, arena allocation, and NUMA-aware memory management for specialized use cases.
string_view and span
C++17 introduced std::string_view (non-owning string reference) and C++20 added std::span (non-owning contiguous sequence view). These types eliminate copies when passing read-only references to strings and arrays:
void parse(std::string_view sv) {
// No copy — sv points to the original string
---
std::string s = "hello";
parse(s); // Implicit conversion, no allocation
———–|——–|——-|——|—————|
| vector | O(n) | O(n) | O(n) | O(1) |
| deque | O(1)* | O(n) | O(n) | O(1) |
| list | O(1) | O(1) | O(n) | O(n) |
| set/map | O(log n) | O(log n) | O(log n) | — |
| unordered_set/map | O(1) avg | O(1) avg | O(1) avg | — |
- Use
vectorby default — it’s cache-friendly and fast - Use
unordered_mapfor key-value lookups (hash table) - Use
set/mapwhen you need ordering - Use
listonly when you need O(1) insert/erase at arbitrary positions
Conclusion
The STL is the foundation of modern C++. Master vector, unordered_map, and set as your go-to containers. Learn the algorithm functions — sort, find, transform, accumulate — they eliminate loops and make your code clearer. Use iterators to bridge containers and algorithms, and always prefer STL algorithms over handwritten loops.
Related: Check our C++ OOP guide.
For a comprehensive overview, read our article on C File Io Guide.