C++ Standard Library · Lesson 4

std::map & std::unordered_map

Look things up by key, not by position. The ordered tree vs the hashed table — how each works, the operator[] trap, and which to default to.

🎯 Reach for key→value lookup by reflex
The one idea

When you need to look something up by key (not by index), use an associative container. std::unordered_map hashes keys for O(1) average lookup; std::map keeps keys sorted in a balanced tree for O(log n). Default to unordered_map unless you need order.

01 The problem they solve

A vector is great when you know where something is (its index). But "give me the score for player "Ada"" has no index — searching a vector for it is O(n). Maps turn that into a fast, direct lookup keyed on the value you actually have.

#include <unordered_map>
#include <string>

std::unordered_map<std::string, int> score;   // key: name, value: points

score["Ada"]   = 42;        // insert
score["Grace"] = 37;
score["Ada"] += 5;        // update → 47

std::cout << score["Ada"];    // 47, found in ~O(1)
Run on godbolt.org (-std=c++17+).
💡 Mental model A map is a dictionary: a set of unique keys, each mapped to one value. map<K,V> stores entries as std::pair<const K, V> — the key is const because changing it would break the structure.

02 The everyday operations

std::map<std::string, int> m;

m["a"] = 1;                 // insert or assign
m.insert({"b", 2});          // insert (does nothing if "b" exists)
m.at("a");                   // 1   — throws std::out_of_range if absent

// existence check (DON'T use [] for this — see §03)
if (m.contains("a")) { }      // C++20
if (m.find("a") != m.end()) { }   // pre-C++20, works everywhere
m.count("a");                // 0 or 1 (keys are unique)

m.erase("a");                // remove by key
m.size();                    // number of entries

// iterate — structured bindings (C++17) unpack each pair
for (const auto& [key, value] : m)
    std::cout << key << " = " << value << "\n";
// for std::map this prints in sorted key order: a, b, ...

Structured bindings

auto [key, value] : m destructures each pair into named variables — far cleaner than it->first / it->second. (Lesson 11 covers pair/tuple.)

find returns an iterator

find(k) gives an iterator to the entry, or end() if absent. Deref it to get the pair: it->second is the value.

Keys are unique

Inserting an existing key is a no-op (the value isn't replaced by insert). Want duplicates? That's multimap (§05).

Source: cppreference — std::map · std::unordered_map.

03 The operator[] trap

This is the #1 map surprise: m[key] inserts a default-constructed value if the key is missing — it never "just fails." So using it to check for a key silently grows your map.

std::map<std::string, int> m;

if (m["missing"] == 0) { }    // ✗ just INSERTED {"missing", 0}! m.size() is now 1

if (m.contains("missing")) { }   // ✓ checks without inserting (C++20)
if (m.find("missing") != m.end()) { }   // ✓ same, any standard
⚠ Two more [] facts (1) operator[] doesn't exist on a const map (it might insert), so use at() or find() there. (2) The auto-insert behavior is actually handy for counting: ++count[word] works because a missing word starts at 0.
// word frequency in one line, thanks to []'s auto-insert:
std::unordered_map<std::string, int> freq;
for (const auto& w : words) ++freq[w];   // missing → 0, then ++

Source: cppreference — map::operator[] inserts if the key does not exist.

04 Ordered (tree) vs unordered (hash)

Both map keys to values; they differ in how they store entries — and that drives everything.

std::map — balanced tree m d t a g sorted order · O(log n) · needs only < std::unordered_map — hash table hash(key) % N → bucket 0 1 2 3 "Ada":47 "Eve":9 "Bo":3 no order · avg O(1) · needs hash + ==
Tree gives you order for a log-factor price; hash gives you speed but scatters the keys. Pick based on whether you need sorted iteration.
std::mapstd::unordered_map
Structurebalanced binary search treehash table (buckets)
Lookup / insert / eraseO(log n)O(1) average, O(n) worst
Iteration ordersorted by keyunspecified / "random"
Key requirementordered (<)hashable (std::hash) + ==
Reach for it when…you need sorted order or range queriesyou just need fast lookup (the common case)
🎯 The default If you don't care about order — and usually you don't — unordered_map is faster. Choose map when you need keys in sorted order, or to iterate a range of keys. Built-in types and std::string are hashable out of the box.

05 The rest of the family

Every map/set comes in four flavors built from two axes — ordered vs hashed, and unique vs duplicate keys:

Unique keysDuplicate keys allowed
Orderedmap, setmultimap, multiset
Hashedunordered_map, unordered_setunordered_multimap, unordered_multiset

A set is just a map with keys and no values — "is this in my collection?" Lesson 5 covers sets and pulls all of this into a single container-selection guide. multimap lets one key map to many values (e.g. tags → posts).

Source: cppreference — associative & unordered associative containers.

06 Check yourself

You need fast lookup by string key and don't care about iteration order. Default choice?
What's wrong with if (m["key"] == 0) as an existence check?
Which is true of std::map but not std::unordered_map?
The clean way to existence-check without inserting, on any standard, is:

07 Flashcards

Q: When do you reach for an associative container instead of a vector?
A: When you look things up by key rather than by index/position. Vector find is O(n); a map gives O(1) avg (unordered) or O(log n) (ordered).
Q: map vs unordered_map — the core trade?
A: map = balanced tree, keys sorted, O(log n). unordered_map = hash table, no order, O(1) average. Default to unordered unless you need order.
Q: What does m[key] do when key is absent?
A: Inserts a default-constructed value for that key and returns a reference to it. So never use [] to test existence — use contains/find.
Q: How do you count word frequencies in a few lines?
A: unordered_map<string,int> f; for (auto& w : words) ++f[w]; — [] default-inserts 0 for a new word, then increments.
Q: How do you iterate a map cleanly (C++17)?
A: Structured bindings: for (const auto& [k, v] : m). Each entry is a pair<const K, V>.
Q: What's a multimap?
A: A map that allows duplicate keys — one key can map to many values. Same idea as map but without the unique-key constraint.
👩‍🏫 I'm your teacher — ask me anything

Try: "how do I make my own struct usable as an unordered_map key?" (hint: provide std::hash + ==), or "show me grouping items with multimap".