Standard Library[lib]
C++ iterator: how begin() and end() work
- Section
- Standard Library [lib]
- Standard
- C++17
- Compilers
- MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
- Published
- Length
- 7 min read
In this answer8 clauses
- 1How do begin() and end() work?
- 2Iterator or range-for: which should you use?
- 3How do you move an iterator or measure a distance?
- 4What are const and reverse iterators?
- 5How do you erase elements while iterating?
- 6When does a vector iterator stop being valid?
- 7Why do algorithms take two iterators?
- 8An iterator cheat sheet
Abstract
A C++ iterator is an object that points at one element of a container: *it reads that element and ++it moves to the next. begin() points at the first element and end() just past the last, which is not an element, so a loop runs while it != end(). To erase in a loop, write it = v.erase(it), and after a push_back, insert or erase on a vector, get the iterator again instead of keeping an old one.

A C++ iterator is an object that points at one element of a container. v.begin() returns an iterator to the first element and v.end() one just past the last; ++it moves to the next element and *it reads that element. Containers such as std::vector hand them out, and algorithms such as std::sort take a pair of them.
The classic loop over a vector, in a whole program:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers{10, 20, 30, 40, 50};
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << '\n';
}
}— end example]
It printed:
10
20
30
40
50— end example]
The answer is written for C++17 (Since When’s C++17 table gives the first GCC, Clang and MSVC releases of its features). The samples were built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++), and each one shown here printed the same on all four.
How do begin() and end() work?
begin() returns an iterator to the first element, and end() one to the position just past the last, which is not an element. Reading *numbers.end() is undefined behavior, which means the standard sets no rule for what happens, and the library never assumes a past-the-end iterator can be read. In an empty vector the two are equal, so a loop that runs while it != numbers.end() runs zero times:
std::vector<int>::iterator first = numbers.begin();
std::cout << *first << '\n'; // 10
auto last = std::prev(numbers.end()); // one step back from end()
std::cout << *last << '\n'; // 50
std::vector<int> empty;
std::cout << std::boolalpha;
std::cout << (empty.begin() == empty.end()) << '\n'; // true— end example]
It printed 10, 50 and true. The type is std::, spelled out for first; auto writes it for you, as it does for last.

Iterator or range-for: which should you use?
A range-for is the same loop with the iterator written for you by the compiler, so use it to visit every element. Use an iterator when you need the position itself: to erase or insert there, or to hand part of a container to an algorithm. Writing through *it changes the element; in a range-for, int n is a copy and int& n is the element itself:
for (int n : numbers) { // the same loop, written for you
std::cout << n << '\n';
}
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
*it = *it * 2; // writes through the iterator
}— end example]
The first loop printed 10 to 50; after the second, the vector held 20, 40, 60, 80 and 100.
How do you move an iterator or measure a distance?
Use std::next(it, n) to move forward, std::prev(it) to step back and std:: to count the steps between two iterators, all from <iterator>. On a vector, it + 2 works too, because its iterators can jump. Here items is a std::list<int> holding the same five values:
auto third = numbers.begin() + 2; // a vector can jump
std::cout << *third << '\n'; // 30
std::cout << *std::next(numbers.begin(), 3) << '\n'; // 40
std::cout << *std::prev(numbers.end()) << '\n'; // 50
std::cout << std::distance(numbers.begin(), third) << '\n'; // 2
auto it = std::next(items.begin(), 2); // steps twice
std::cout << *it << '\n'; // 30
std::cout << *std::prev(it) << '\n'; // 20
std::cout << std::distance(items.begin(), it) << '\n'; // 2— end example]
All four printed what the comments show. std::next returns a moved copy and leaves it where it was; std::distance takes constant time on a vector and one step per element on a list. On a std::list, + does not compile:
auto it = items.begin() + 1; // error: a list cannot jump— end example]
GCC stops there with error: no match for 'operator+' (operand types are 'std::. Which moves work depends on the container:
| How it moves | Containers |
|---|---|
| Forward only (all the standard promises) | std::forward_list, the unordered containers |
| Both ways, one step at a time | std::list, std::set, std::map |
Jumps anywhere, so it + n works |
std::vector, std::array, std::deque, std::string |
The standard promises the unordered containers, such as std::unordered_map, only forward steps; Microsoft’s library marks their iterators as able to step back, so portable code must not.
What are const and reverse iterators?
cbegin() and cend() return a const_iterator, which reads elements but cannot change them; rbegin() and rend() return reverse iterators, which walk the container from the last element to the first:
auto first = numbers.cbegin(); // a const_iterator
std::cout << *first << '\n'; // 10: reading is fine
for (auto it = numbers.rbegin(); it != numbers.rend(); ++it) {
std::cout << *it << '\n'; // 50 down to 10
}— end example]
It printed 10, then 50 down to 10. Writing through a const_iterator, as in *it = 99;, does not compile; GCC’s error starts assignment of read-only location.
How do you erase elements while iterating?
Assign what erase returns. erase removes the element and returns an iterator to the one after it, so write it = numbers.erase(it); and step with ++it only when nothing was erased:
for (auto it = numbers.begin(); it != numbers.end();) {
if (*it > 25) {
it = numbers.erase(it); // now at the next element
} else {
++it; // step only when nothing was erased
}
}— end example]
It left 10 and 20. The for line has no ++it of its own: after an erase, it already points at the next element, so stepping it again would skip that one, or step past end() when the last element was erased.
Since C++20, std::erase_if does the whole loop in one call: pass the vector and a condition, here the lambda (a function written in place) [](int n) { return n > 25; }, and it returns how many elements it removed: 3 here.
When does a vector iterator stop being valid?
When the vector grows past its capacity, the number of elements it has room for: it moves its elements to a new block of memory, and every iterator taken before then points at the old one. Without that move, a push_back, insert or erase still invalidates the iterators from the position it changed onward, end() included. On all three standard libraries the five-element vector started full, so one push_back was enough:
std::cout << std::boolalpha;
std::cout << "full: "
<< (numbers.size() == numbers.capacity()) << '\n';
const int* before = numbers.data(); // where the elements live
auto old_capacity = numbers.capacity();
numbers.push_back(60); // no room left: a new block of memory
std::cout << "grew: " << (numbers.capacity() > old_capacity)
<< '\n';
std::cout << "moved: " << (numbers.data() != before) << '\n';— end example]
All four printed full: true, grew: true and moved: true. An iterator taken before the push_back must not be used after it: every iterator is invalidated when the capacity changes. After a push_back, insert or erase, get the iterator again instead of keeping an old one.
Why do algorithms take two iterators?
A pair of iterators names a range, from the first up to but not including the second, so the same algorithm works on part of a container, and on any container whose iterators make the moves it needs: std::sort needs ones that jump. Here numbers starts as 50, 30, 10, 40, 20:
std::sort(numbers.begin(), numbers.begin() + 3); // first three
for (int n : numbers) {
std::cout << n << '\n'; // 10 30 50, then 40 20 untouched
}
auto it = std::find(numbers.begin(), numbers.end(), 40);
if (it != numbers.end()) {
std::cout << std::distance(numbers.begin(), it) << '\n'; // 3
}— end example]
It printed 10, 30, 50, 40, 20 and then 3. Algorithms that search answer with an iterator: std::find returned one to 40, and std::distance turned it into an index. When nothing matches, std::find returns the second iterator it was given, here end(), so compare with that before reading through the result. The other containers and algorithms are in Standard library.
An iterator cheat sheet
| Write | What it does |
|---|---|
v.begin() |
points at the first element |
v.end() |
points one past the last; never read it |
*it |
reads the element it points at |
it->member |
reads a member of the element, like it->first |
++it |
moves to the next element |
std::next(it, n) |
returns a copy moved n steps |
std::distance(a, b) |
counts the steps from a to b |
v.cbegin() |
points at the first element, read-only |
v.rbegin() |
points at the last, walking backwards |
it = v.erase(it) |
erases, then points at the next element |
Questions
What is the difference between an iterator and a pointer?
A pointer into an array is one kind of iterator: * reads the element, ++ moves to the next, and it can jump straight to any element, like a vector’s iterator. An iterator is the general idea: a std::list or std::map iterator is an object that knows how to step to the next element of that container, with the same * and ++, without being a plain address.
What does end() return in C++?
An iterator to the position one past the last element, the past-the-end iterator. It is not an element, so reading *v.end() is undefined behavior. In an empty container begin() equals end(), so a loop that runs while it != v.end() never starts.
What is the type of a vector iterator?
For a std::vector<int> it is std::, and std:: for the one cbegin() returns. Writing auto it = numbers.begin(); gives it the same type without spelling it out.
How do you get the index from an iterator?
Call std::distance(v.begin(), it), from <iterator>: it counts the steps from the first element to it. It takes constant time on a vector and one step per element on a std::list.
How do you iterate over a map in C++?
Use the same loop, for (auto it = ages.begin(); it != ages.end(); ++it), with std::cout << it->first << “: “ << it->second << ‘\n’; inside it. it->first is the key and it->second the value. A std::map visits its keys in sorted order: filled with Mia, Ben and Ana, it printed Ana: 41, Ben: 29 and Mia: 34.
What are the iterator categories in C++?
Six: input, output, forward, bidirectional, random access and, since C++17, contiguous. A std::forward_list iterator is forward, a std::list or std::map iterator bidirectional, a std::deque iterator random access, and a std::vector, std::array or std::string iterator contiguous: random access over elements kept in one block of memory.
Sources
- Iterator librarycppreference.com
- std::nextcppreference.com
- std::distancecppreference.com
- std::vector::erasecppreference.com
- std::erase, std::erase_if (std::vector)cppreference.com
- std::vector (Iterator invalidation)cppreference.com
- [iterator.requirements.general] Iterator requirements: generalC++ standard working draft