C/C++
Iterating Over a std::map in C++
{/_ Secondary: range-based for, iterators (begin/end/cbegin), std::for_each, lambda, structured bindings (C++17), reverse_iterator (rbegin/rend), auto, std::pair, const-correctness, erase-during-iteration. Context: STL, header <map>, balanced BST, std::unordered_map, O(log n), C++11/14/17, GCC/Clang/MSVC, structured bindings. Course: CS106B (Stanford), 6.087 (MIT), CS104 (USC), generic data-structures courses. Competing: cppreference, GeeksforGeeks, Stack Overflow, ChatGPT. Info-gain: working corrected code, complexity per technique, the const/reference pitfall table, erase-safe loop, named developer + 50/50 payment. _/}

A std::map in C++ iterates in ascending key order, and the cleanest way to walk it is a C++17 range-based for loop with structured bindings: for (const auto& [key, value] : myMap). That single line covers most real code. The other five techniques on this page each earn their place in a specific situation: modifying values, erasing entries safely, running an algorithm across the container, or walking it backward. This guide from the C++ team at GeeksProgramming gives you working code for all six, the time cost of each, and the two bugs that fail more student submissions than any logic error.
What a std::map stores and how it orders keys
A std::map stores sorted key-value pairs, one value per unique key, in a balanced binary search tree. The container lives in the <map> header and is part of the Standard Template Library. Every key is unique, and the map keeps its elements ordered by key the whole time, using std::less by default. That ordering is the property that shapes every iteration on this page: when you walk a map start to end, you get keys in ascending sequence, not insertion order.
The tree structure sets the performance profile. Lookup, insertion, and deletion each run in O(log n) because the map searches the tree by halving the candidates at every node. Iteration itself is O(n): each ++it steps to the next key in sorted order, and visiting every element costs one pass. If sorted traversal matters, that O(log n) lookup is the price you pay for it. When you only need fast access and order is irrelevant, std::unordered_map trades sorting for average O(1) lookup and iterates in bucket order instead.
Here is a map of student names to grades, the example we reuse below:
#include <map>
#include <string>
#include <iostream>
int main() {
std::map<std::string, double> studentGrades;
// Insert with insert(), with emplace(), and with operator[]
studentGrades.insert(std::make_pair("Alice", 92.5));
studentGrades.emplace("Bob", 85.0);
studentGrades["Charlie"] = 77.8;
// Read a value back by key
double aliceGrade = studentGrades["Alice"]; // 92.5
std::cout << "Alice: " << aliceGrade << '\n';
return 0;
}
One trap hides in that last access. operator[] inserts a default-constructed value when the key is absent, so reading a missing key silently grows the map. To read without that side effect, use studentGrades.at("Alice"), which throws std::out_of_range for a missing key, or studentGrades.find("Alice"), which returns end() when nothing matches.
Technique 1: Range-based for loop with structured bindings
A range-based for loop with structured bindings is the default choice for iterating a std::map in modern C++. Structured bindings arrived in C++17 and let you name the key and value directly, so the loop body never mentions .first or .second. The const auto& binding reads each element by reference and forbids changes, which is what you want for a print or sum pass.
#include <map>
#include <string>
#include <iostream>
int main() {
std::map<std::string, int> fruitCounts = {
{"apple", 1}, {"banana", 2}, {"cherry", 3}
};
for (const auto& [name, count] : fruitCounts) {
std::cout << name << ": " << count << '\n';
}
// Output (sorted by key):
// apple: 1
// banana: 2
// cherry: 3
return 0;
}
The output prints in key order because the map is sorted. To edit values in place, drop const and bind by mutable reference:
for (auto& [name, count] : fruitCounts) {
count *= 10; // writes to the original element, not a copy
}
The key in a structured binding is always const, even when you write auto&. Changing a key would move the node within the tree and break the ordering invariant, so the language forbids it. The mapped value stays writable. That split is exactly the behavior you want: stable keys, editable values.
Technique 2: Explicit iterators with begin() and end()
Explicit iterators give you the most control and reveal what the range-based loop hides. An iterator is an object that points at one element and advances with ++. You start at begin(), stop when you reach end(), and read the current pair through the iterator. For a map, the iterator points at a std::pair, so it->first is the key and it->second is the value.
#include <map>
#include <string>
#include <iostream>
int main() {
std::map<std::string, int> scores = {{"x", 10}, {"y", 20}, {"z", 30}};
for (auto it = scores.begin(); it != scores.end(); ++it) {
std::cout << it->first << " = " << it->second << '\n';
}
return 0;
}
The auto keyword deduces the iterator type, which spelled out in full is std::map<std::string, int>::iterator. Writing that by hand is verbose and breaks whenever the key or value type changes, so auto is the standard practice. For a read-only walk, prefer cbegin() and cend(), which return const_iterator and stop you from editing values by accident.
Explicit iterators matter most when you need the iterator object itself, not just the values it points at. Erasing during iteration is the headline case, and it has a correct form and a wrong one.
// Erase every entry whose value is below 15, safely
for (auto it = scores.begin(); it != scores.end(); /* no ++ here */) {
if (it->second < 15) {
it = scores.erase(it); // erase returns the next valid iterator
} else {
++it;
}
}
map::erase returns the iterator following the removed element since C++11. Reassigning it to that return value keeps the loop valid. The classic bug is calling scores.erase(it) and then ++it on the now-invalidated iterator, which is undefined behavior. Move the increment inside the else branch and the loop is correct.
Technique 3: std::for_each with the algorithm header
std::for_each applies one callable to every element between two iterators, no manual loop required. It lives in the <algorithm> header. You pass the map's begin() and end(), plus a function or lambda that receives each element. This style suits code that already leans on the standard algorithms, since it reads as a single statement of intent.
#include <map>
#include <string>
#include <iostream>
#include <algorithm>
int main() {
std::map<int, std::string> menu = {{1, "apple"}, {2, "banana"}, {3, "cherry"}};
std::for_each(menu.begin(), menu.end(), [](const auto& pair) {
std::cout << pair.first << ": " << pair.second << '\n';
});
return 0;
}
The lambda takes each std::pair by const reference and prints both halves. std::for_each does not let you break out early the way a plain loop does, so reach for it when you genuinely visit every element. When you need an early exit, a range-based for loop with a break is the better fit.
Technique 4: Lambda expressions for the callable
A lambda expression defines an unnamed function inline, which pairs naturally with std::for_each and the other algorithms. The earlier example already used one. The flexibility shows up when you name the lambda, reuse it, or capture surrounding variables so the loop body can read and write outside state.
#include <map>
#include <string>
#include <iostream>
#include <algorithm>
int main() {
std::map<int, std::string> menu = {{1, "apple"}, {2, "banana"}, {3, "cherry"}};
int charCount = 0;
// Capture charCount by reference so the lambda updates it
std::for_each(menu.begin(), menu.end(), [&charCount](const auto& [id, name]) {
charCount += static_cast<int>(name.size());
});
std::cout << "Total characters across values: " << charCount << '\n';
return 0;
}
The capture list [&charCount] binds the outside variable by reference, so the lambda accumulates a running total. The parameter uses structured bindings too, so id and name read clearly inside the body. Lambdas earn their keep when the per-element work is more than a print: filtering, transforming, or feeding a separate accumulator. For deeper coverage of the type system that makes generic lambdas and algorithms work, see our guide on templates in C++.
Technique 5: Reverse iteration with rbegin() and rend()
Reverse iterators walk a std::map from the largest key down to the smallest. Call rbegin() for the iterator at the last element and rend() for the position before the first. The map stays sorted, so reverse iteration gives you descending key order without sorting anything yourself. This is the path for "process the highest keys first" tasks: top scores, latest timestamps stored as keys, or any descending report.
#include <map>
#include <string>
#include <iostream>
int main() {
std::map<int, std::string> menu = {{1, "apple"}, {2, "banana"}, {3, "cherry"}};
for (auto rit = menu.rbegin(); rit != menu.rend(); ++rit) {
std::cout << rit->first << ": " << rit->second << '\n';
}
// Output:
// 3: cherry
// 2: banana
// 1: apple
return 0;
}
A reverse iterator still exposes ->first and ->second, so the access pattern matches a forward iterator. Note that ++rit moves toward the front of the map, which feels backward at first but is the whole point: incrementing a reverse iterator steps to the next-smaller key.
Technique 6: The auto keyword as a habit, not a technique
The auto keyword is less a separate way to iterate than a discipline that improves every loop above. It tells the compiler to deduce the iterator or element type for you, which removes the verbose std::map<Key, Value>::iterator boilerplate and keeps the loop stable when types change. Every example on this page already uses it.
#include <map>
#include <string>
int main() {
std::map<std::string, int> data = {{"a", 1}, {"b", 2}};
// auto deduces std::map<std::string, int>::iterator
for (auto it = data.begin(); it != data.end(); ++it) {
int& value = it->second; // reference, edits the live element
value += 1;
}
return 0;
}
The one rule that comes with auto: it strips references and const by default. auto x = it->second; copies the value; auto& x = it->second; aliases it. That distinction is the source of the most common map-iteration bug, which the next section covers in full.
The two bugs that fail student submissions
The accidental copy and the unsafe erase fail more map loops than any algorithm mistake. Both are quiet: the code compiles, runs, and produces almost-right output or undefined behavior that only shows on the grader's hidden inputs. The table below pairs each pitfall with the fix.
| Pitfall | What happens | The fix |
| ------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------- |
| for (auto [k, v] : m) | Copies every pair each step; edits hit the copy, not the map | for (auto& [k, v] : m) to edit, const auto& to read |
| m.erase(it); ++it; | Increments an invalidated iterator (undefined behavior) | it = m.erase(it); and ++it only when you keep the element |
| m[key] to read | Inserts a default value for a missing key, growing the map | m.at(key) (throws) or m.find(key) (returns end()) |
| Editing the key in a binding | Will not compile; the key is const for a reason | Erase the old entry and insert a new key-value pair |
| std::for_each with early exit | No break; the algorithm visits every element | Use a range-based for loop when you need to stop early |
The copy bug is the one to internalize. With a std::map<std::string, BigObject>, leaving off the & copies a heavy object on every iteration, which turns an O(n) walk into an O(n) walk plus n deep copies. Add the reference and the slowdown disappears.
Which technique to reach for
Match the technique to the job rather than memorizing all six. The quick rules:
- Reading or printing every pair: range-based for with
const auto& [key, value]. Shortest, clearest, no copies. - Editing values in place: the same loop with
auto& [key, value]. The key stays const, the value is writable. - Erasing while you walk: explicit iterators with
it = m.erase(it). The range-based loop cannot do this safely. - Applying a standard algorithm:
std::for_eachwith a lambda, when you visit the whole container with no early exit. - Descending key order: reverse iterators via
rbegin()andrend(). - Every case above:
autofor type deduction, with the right reference and const qualifier.
std::map is one container in a wider family. For the dynamic-array workhorse, read vectors in C++, and for the bigger picture of containers, iterators, and algorithms together, start with our introduction to the C++ Standard Template Library.
Frequently asked questions
What is the simplest way to iterate over a std::map in C++?
A C++17 range-based for loop with structured bindings reads cleanest: for (const auto& [key, value] : myMap). It unpacks each pair into named variables, so you never touch .first or .second. Add const auto& to avoid copying every node. This is the form most teams use today.
In what order does a std::map iterate?
A std::map iterates in ascending key order, sorted by std::less by default. The container is a balanced binary search tree, so begin() points to the smallest key and the walk visits keys in sorted sequence. If you need insertion order or no ordering at all, use a different container such as std::unordered_map with a separate index.
What is the difference between iterating over a map and an unordered_map?
The syntax is identical: both expose begin(), end(), and structured bindings. The difference is order and cost. std::map yields keys sorted, with O(log n) lookup. std::unordered_map yields keys in unspecified bucket order, with average O(1) lookup. Pick map for sorted traversal, unordered_map for fast access.
How do I modify map values while iterating?
Bind the value by non-const reference: for (auto& [key, value] : myMap) value += 1;. The key stays const because changing it would break the tree ordering, but the mapped value is writable. Binding by value only edits a copy, so the map stays unchanged.
How do I erase elements from a map during iteration?
Use the iterator that erase returns: it = myMap.erase(it) when you remove, and ++it otherwise. Since C++11, map::erase returns the iterator after the erased element. Calling erase(it) and then ++it on the same iterator is undefined behavior.
Should I use auto or write out the iterator type?
Use auto. Writing std::map<std::string, int>::iterator by hand is verbose and breaks when the key or value type changes. auto deduces the exact type and keeps the loop readable. Reserve the spelled-out type for explaining the mechanics.
Why does my range-based loop copy every map element?
Because the loop reads for (auto [key, value] : myMap) with no reference. That copies each std::pair on every step, which is slow for string or object values. Add const auto& for read-only access or auto& to mutate. The missing reference is the most common map-iteration fix in student code.
Can I iterate a std::map in reverse order?
Yes. Call rbegin() and rend() for reverse iterators that walk from the largest key down to the smallest: for (auto rit = myMap.rbegin(); rit != myMap.rend(); ++rit). Reverse iterators still expose ->first and ->second, so the access pattern matches a forward iterator.
Stuck on a C++ assignment?
When a map loop compiles but the autograder still marks it wrong, the cause is usually a hidden edge case, an off-by-one in the iteration, or output formatting the spec never spelled out. The C++ developers at GeeksProgramming have helped students since 2014, with a 95% first-attempt pass rate and a 4.7/5 rating from 350+ reviews across Google and other platforms. Send the brief, pay 50% to start and 50% after the code runs on your version, and get back working code with a walkthrough you can defend. Browse the full C++ programming assignment help service for languages, pricing, and turnaround.
Related articles
- C/C++
Vectors in C++: A Complete Guide
Master std::vector in C++ with declaration, push_back and emplace_back, iterators, size vs capacity, 2D vectors, custom types, and complexity, with code that compiles.
Aug 7, 2023
- C/C++
Min Heap and Max Heap in C++
Build min heaps and max heaps in C++ with std::priority_queue and the STL heap algorithms, plus array math, heapify, heap sort, and worked examples.
Sep 19, 2023
- C/C++
Python vs C++: Which Should You Learn?
A direct comparison of Python and C++ across syntax, speed, memory management, OOP, and use cases to help you pick the right language.
Sep 17, 2023


