What a hash map is, and what C++ calls it
A hash map stores key-value pairs and finds the value for a key quickly, without scanning everything. It hashes the key to decide where to look, so a lookup, an insert or a delete takes constant time on average, however many entries the map holds.
C++ has no type called hashmap or HashMap. The hash map is std::unordered_map, declared in the <unordered_map> header. The older hash_map that some tutorials mention is a pre-standard extension; use unordered_map instead. Its sibling std::map keeps keys sorted, is built on a balanced tree, and takes O(log n) per operation.
The examples below assume these lines at the top of the file:
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
Declaring and initializing a hash map in C++
The type takes a key type and a value type. An empty map that maps strings to integers:
unordered_map<string, int> digitValue;
To start with some entries, pass a brace list of {key, value} pairs. Both of these forms work:
unordered_map<string, int> a({
{"zero", 0},
{"one", 1}
});
unordered_map<string, int> b = {{"zero", 0}, {"one", 1}};
Accessing values
There are three ways to read a value, and they differ in what happens when the key is missing.
m[key]returns a reference to the value. If the key is absent, it inserts the key with a default value (0 for numbers, an empty string for strings) and returns that.m.at(key)returns the value, and throwsstd::out_of_rangeif the key is absent. It never inserts.m.find(key)returns an iterator, which equalsm.end()when the key is absent. The pair is reached withit->firstandit->second.
unordered_map<string, int> ages = {{"Ana", 31}, {"Ben", 27}};
auto it = ages.find("Ben");
if (it != ages.end()) cout << it->first << " is " << it->second << "\n"; // Ben is 27
cout << ages.size() << "\n"; // 2
cout << ages["Dan"] << "\n"; // 0
cout << ages.size() << "\n"; // 3: reading "Dan" added it
The insertion behaviour of [] is the most common source of bugs with maps. A lookup that you meant as a question quietly grows the map. To ask without changing anything, use count, find or at.
Adding and updating entries
Assigning through [] adds the key when it is new and overwrites the value when it already exists:
digitValue["three"] = 3; // adds
digitValue["zero"] = 5; // updates
digitValue["one"] += 10; // read and update in one step
insert behaves differently: m.insert({"one", 100}) does nothing if "one" is already there. Use it when you do not want to overwrite.
Because m[key] starts a missing value at zero, counting is a one-liner: freq[word]++ works whether or not the word was seen before. This is the standard way to build a frequency table.
Removing entries
The method is erase, not remove. It returns the number of entries removed, so erasing a missing key is harmless and returns 0:
digitValue.erase("zero"); // returns 1
digitValue.erase("missing"); // returns 0, nothing happens
Checking whether a key exists
count(key) returns 1 if the key is present and 0 otherwise, because a map holds each key at most once. It converts to a boolean, so you can write if (m.count(key)). find does the same check and also gives you the entry. The member function contains exists only from C++20, so it does not compile in C++17.
Iterating over a hash map
A range-based for loop visits every entry. Each element is a pair<const Key, Value>, so the loop variable has to be a pair (or auto), not just the key:
for (const auto &[key, value] : digitValue) {
cout << key << " is " << value << "\n";
}
The form [key, value] is a structured binding (C++17). Writing for (string key : digitValue) is a compile error, since a pair cannot convert to a string. To change values while looping, drop the const and bind by reference: for (auto &[k, v] : m) v++;.
The order of iteration is unspecified. It is not insertion order and it is not sorted, and it can change after inserts. If you need a stable order, use std::map, or copy the keys into a vector and sort them. Iterating takes O(n).
Time complexity
| Operation | Average | Worst case |
|---|---|---|
Insert, update, [], at, find, count, erase | O(1) | O(n) |
| Iterate over all entries | O(n) | O(n) |
The worst case happens when many keys land in the same bucket, which a good hash function makes unlikely. The standard library provides hash functions for integers, strings and other basic types. A pair or a struct as a key does not work out of the box: unordered_map<pair<int,int>, int> fails to compile until you supply your own hash function. Use std::map for such keys when that is simpler.
The exercise, step by step
The lesson tracks a fruit inventory in an unordered_map<string, int> named fruits.
- Create the map with three entries: apples 3, bananas 5, grapes 2, using the brace list.
- Add 4 apples by assigning
fruits["apples"]its current value plus 4. It becomes 7. - Report a missing fruit. Print "Out of pears" when
fruits.count("pears")is 0. Testing withfruits["pears"]would insert pears with a count of 0. - Remove the bananas with
erase. - Print the inventory with a range-based loop and a structured binding.
unordered_map<string, int> fruits({
{"apples", 3},
{"bananas", 5},
{"grapes", 2}
});
fruits["apples"] = fruits["apples"] + 4;
if (!fruits.count("pears")) {
cout << "Out of pears" << endl;
}
fruits.erase("bananas");
for (const auto &[fruit, counter] : fruits) {
cout << "We have " << counter << " "<< fruit << "." << endl;
}
The output is "Out of pears", then the two remaining lines, "We have 7 apples." and "We have 2 grapes.", in either order, because iteration order is unspecified.
Key takeaways
- The C++ hash map is
unordered_map; include<unordered_map>. - Declare it as
unordered_map<KeyType, ValueType>and fill it with a brace list or withm[key] = value. m[key]inserts a missing key. Usecount,findoratto look without inserting.- Remove with
erase, and iterate withfor (const auto &[k, v] : m); the order is not defined. - Operations are
O(1)on average.
