Skip to content

Intermediate · The Standard Template Library (STL) · topic 24 of 42 · 25 min

Iterators: the glue between containers and algorithms

Explanation

An iterator is an object that points to an element of a container and can move to the next one. begin() points to the first element and end() points one PAST the last, so [begin, end) is a half-open range. Algorithms take iterator ranges, which is why one std::sort works on many containers.

Why iterators?

A vector stores elements in a row, a set in a tree, a list in linked nodes. If every algorithm had to know every container's insides, the library would need hundreds of versions of sort and find. Iterators solve this. An iterator is a small object that behaves like a pointer: *it gives the element, ++it moves to the next one. Each container provides its own iterators, and algorithms only talk to iterators.

Think of it like this: An iterator is like a bookmark. Whatever the book (paperback, e-book, comic), you can read the page the bookmark is on and move it to the next page. A reader (the algorithm) only needs to know how to use bookmarks, not how each book is bound.
cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {10, 20, 30};
    for (auto it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << ' ';      // 10 20 30
    }
    auto it = v.begin();
    *it = 99;                          // change through the iterator
    std::cout << '\n' << v[0] << '\n'; // 99
}
A range-for loop is really this loop written for you.

The half-open range [begin, end)

end() does not point to the last element; it points one past it. It is a "stop here" marker and must never be dereferenced. This convention makes empty ranges easy (begin == end) and loop conditions simple (it != end).

Careful: Never write *v.end(), and do not call *v.begin() on an empty container. Both are undefined behaviour. Always check empty() or compare with end() first.

Kinds of iterators

CategoryCan doFound in
Forward++it, *it, read a sequence once or many timesstd::forward_list, unordered containers
Bidirectionalalso --itstd::list, std::set, std::map
Random accessalso it + n, it[n], it2 - it1, <std::vector, std::deque, std::array, raw pointers

Algorithms ask for the category they need. std::sort needs random access, so you cannot std::sort a std::list (use list.sort() instead). std::find only needs to move forward, so it works everywhere.

const iterators and reverse iterators

cpp
std::vector<int> v = {1, 2, 3};
for (auto it = v.cbegin(); it != v.cend(); ++it) { /* *it is read-only */ }
for (auto it = v.rbegin(); it != v.rend(); ++it) std::cout << *it;   // 321
cbegin/cend give const iterators; rbegin/rend walk backwards.

Map iterators

cpp
std::map<std::string, int> stock = {{"dal", 25}, {"rice", 40}};
auto it = stock.find("rice");
if (it != stock.end()) {
    std::cout << it->first << " = " << it->second << '\n';   // rice = 40
    it->second += 10;                                         // value can change; key cannot
}
A map iterator points to a std::pair<const Key, Value>; ->first is the key, ->second is the value.

Erasing while iterating

cpp
std::map<std::string, int> stock = {{"dal", 0}, {"rice", 40}, {"oil", 0}};
for (auto it = stock.begin(); it != stock.end(); ) {
    if (it->second == 0) it = stock.erase(it);   // erase returns the next valid iterator
    else ++it;
}
// stock now has only rice
After erase, the old iterator is invalid; continue from the one erase returns. (In C++20, std::erase_if(stock, ...) does this for you.)

Distance and advance

  • std::distance(first, last) counts the steps between two iterators.
  • std::next(it, n) and std::prev(it, n) give moved copies without changing it.
  • For a vector, it - v.begin() is the index of the element.
Tip: C++20 ranges let you pass the container itself: std::ranges::sort(v) instead of std::sort(v.begin(), v.end()). You will meet ranges in the modern C++ module; iterators are still what happens underneath.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Iterators: the glue between containers and algorithms · 1/6

Iterators

Iterators connect containers to algorithms. They behave like pointers that know how to move through a container.

Prefer a video by a teacher? Find free videos on “c++ stl iterators begin end tutorial erase while iterating” on YouTube ↗

In real life: Cleaning an inventory list

A warehouse system keeps stock per product code in a std::map. Every night it removes products with zero stock and prints the rest. Erasing during the loop must use the iterator that erase returns, or the program would crash on the next step.

cpp
#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> stock = {{"P100", 0}, {"P200", 12}, {"P300", 0}, {"P400", 7}};
    for (auto it = stock.begin(); it != stock.end(); ) {
        if (it->second == 0) it = stock.erase(it);
        else ++it;
    }
    for (const auto& [code, qty] : stock) std::cout << code << ' ' << qty << '\n';
}
// P200 12
// P400 7
The safe erase-while-iterating pattern.

Practice (0/4 done)

Task 1 · Question

For std::vector<int> v = {4, 5, 6}, what is *(v.begin() + 2)?

Task 2 · Question

Which statement about v.end() is true?

Task 3 · Question

Which line has the bug? for (auto it = m.begin(); it != m.end(); ++it) { if (it->second == 0) m.erase(it); }

Task 4 · Practice

Using only iterators (no indexes), write a function that returns the position of the first negative number in a std::vector<int>, or -1 if there is none. Then print the vector in reverse using rbegin/rend.

Quick quiz

Question 1

1.Why can you not call std::sort on a std::list?

Question 2

2.What does an empty container's begin() equal?

Question 3

3.What does it->first give for a std::map iterator?

Question 4

4.What do rbegin() and rend() let you do?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Why can you not call std::sort on a std::list?

Answer: std::sort needs random-access iterators; list iterators are only bidirectional

Use the member function list.sort() instead.

What does an empty container's begin() equal?

Answer: end()

An empty range has begin == end.

What does it->first give for a std::map iterator?

Answer: The key

Map elements are pairs: first is the key, second is the value.

What do rbegin() and rend() let you do?

Answer: Iterate from the last element back to the first

Reverse iterators walk the container backwards.

What does std::remove(v.begin(), v.end(), 0) do on its own?Related

Answer: Moves non-zero elements to the front and returns the new logical end; the size is unchanged

You must call erase with the returned iterator to actually shrink the vector.

From std::vector in depth

Which algorithm keeps equal elements in their original order?Related

Answer: std::stable_sort

stable_sort is stable; sort is not guaranteed to be.

From STL algorithms: sort, find, count, accumulate and binary search

What does an iterator's next() return?Related

Answer: An object { value, done }

next() returns { value, done }.

From Iterators and the iteration protocol

What is std::span<const int> good for?Related

Answer: Passing any contiguous array (vector, std::array, C array) to a function without copying

span is a non-owning view of contiguous elements.

From C++20 ranges, views and other C++20 essentials

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “The Standard Template Library (STL)” before its exam: STL algorithms: sort, find, count, accumulate and binary search.
  2. Module 6 exam: The STL · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Templates, smart pointers and move semantics. 42 topics left in the course before the final exam.

← Previous: set, stack, queue, deque and priority_queue