When Order Doesn't Matter
You just saw std::map, which keeps its keys sorted using a balanced tree and gives you O(log n) operations. But sorting costs something, and a lot of the time you don't care what order the keys come out in - you just want to ask "is this key here, and what's its value?" as fast as possible.
That's what std::unordered_map is for. It's a hash table: it runs each key through a hash function to decide where to store it, which makes insert, lookup, and erase average O(1) instead of O(log n). The trade-off is that iteration order is unspecified - the keys come out in whatever order the buckets happen to hold them.
The interface is almost identical to map on purpose - you can often swap one for the other by changing the type. Include <unordered_map> (not <map>), and the keys come out unsorted.
Inserting and Updating
There are a few ways to put entries in, and they don't all behave the same. The two you'll use most are operator[] and insert:
The key difference: operator[] overwrites an existing value, while insert leaves an existing key untouched. If you're on C++17, insert_or_assign(key, value) gives you "set this no matter what" semantics, and try_emplace(key, args...) constructs the value in place only when the key is new - handy for expensive-to-build values.
The operator[] Gotcha: It Inserts on Read
This is the single most common unordered_map bug, so it gets its own section. m[key] is not a pure read. If the key is missing, it default-constructs and inserts it (an int becomes 0, a string becomes ""), then returns a reference. So code that looks like a lookup quietly grows the map:
seen["y"] created a "y" -> 0 entry just by being mentioned. To test membership without mutating the map, use count (returns 0 or 1) or find:
Rule of thumb: use [] only when you intend to create-or-update. For pure reads, use count, contains (C++20), or find.
find and at: Safe Lookups
find returns an iterator to the entry, or end() if the key is absent. It never inserts, and it lets you reach both the key and value through it->first and it->second in one lookup:
When you know the key should exist and want a hard failure if it doesn't, use at. Unlike [], at does not insert - it throws std::out_of_range for a missing key:
int p = prices.at("pen"); // fine
int q = prices.at("hat"); // throws std::out_of_range - key absent
So you have three lookup styles: [] (inserts), at (throws), and find/count (report presence without touching the map). Pick the one whose failure mode matches your intent.
Iterating and Erasing
A range-based for loop with structured bindings is the clean way to walk every entry. Remember the order is arbitrary - never rely on it:
To remove an entry, erase takes a key directly and returns how many were removed (0 or 1). One important gotcha: erasing while iterating invalidates only the erased element's iterator in an unordered_map, so use the return value of erase(it) to advance safely:
Writing wins.erase(it++) style or ++it after a plain erase(it) is the classic dangling-iterator trap - the erased iterator is dead, so always take erase's returned iterator.
map or unordered_map?
Both store key-value pairs with a similar API, so the choice comes down to what you need:
// std::map -> sorted keys, O(log n), range queries (lower_bound)
// std::unordered_map -> no order, O(1) avg, fastest plain lookup
Reach for unordered_map when you just need fast lookup by key and order is irrelevant (counting word frequencies, caching, deduplication). Choose map when you need keys in sorted order, want to iterate in order, or do range queries. Two caveats for unordered_map: its O(1) is an average - a bad hash can degrade it - and a custom key type needs a hash specialization or a hash functor, whereas map only needs operator<.
Next: Set
You've now seen both flavors of key-value container. Sometimes, though, you don't need a value at all - you only care whether something is present, like a collection of unique tags or visited IDs. Next we'll look at std::set (and its hash-based cousin unordered_set), which store just keys and automatically keep them unique.
Frequently Asked Questions
What is the difference between map and unordered_map in C++?
std::map is a balanced binary tree: keys stay sorted and operations are O(log n). std::unordered_map is a hash table: keys are in no particular order, but insert and lookup are average O(1). Use unordered_map when you only need fast key lookup and don't care about order; use map when you need sorted iteration or range queries.
Does unordered_map[] insert a key if it doesn't exist?
Yes. m[key] default-constructs and inserts the key if it's missing, then returns a reference to it. That means even a read-looking if (m[key] == ...) silently grows the map. To check membership without inserting, use m.count(key) or m.find(key) instead.
Is unordered_map always faster than map in C++?
No. It's average O(1), but hashing has overhead and a bad hash (or adversarial keys) can degrade lookups to O(n). For small maps the constant factors and worse cache behavior can make a sorted map just as fast or faster. Benchmark if it matters, but reach for unordered_map by default when you only need key lookup.