Lesson 40
Unordered Map
Covers creating and initializing unordered maps, characteristics, accessing elements, common operations, and iterating through unordered maps.
Lesson content
What is Unordered Map in C++?
An unordered map is an associative container in the Standard Template Library (STL) that stores key-value pairs using a hash table. Each key is unique, and every key maps to exactly one value. Since a hash table is used internally, elements are not stored in sorted order.
Think of an unordered map as a phone contact list stored in a digital database. Instead of searching alphabetically, the system quickly locates a contact using an internal indexing mechanism (hashing), making lookups much faster.
Why does Unordered Map exist?
Although std::map automatically sorts keys, maintaining that order requires extra processing. If sorting isn't necessary, an unordered map provides several benefits.
- Faster average lookup
- Faster average insertion
- Faster average deletion
- Efficient storage of key-value pairs
Real-world use cases
- User account management
- Employee databases
- Product catalogs
- Caching systems
- Word frequency counting
- Database indexing
- DNS lookups
- Session management
- Compiler symbol tables
Prerequisites
- Variables and data types
- Arrays and loops
- Functions
- Templates
- Basic STL containers
- std::map (recommended)
Including the header
#include <unordered_map>Creating an unordered map
unordered_map<key_type, value_type> map_name;
// Example:
unordered_map<int, string> students;Initializing an unordered map
unordered_map<int, string> students =
{
{101, "Alice"},
{102, "Bob"},
{103, "Charlie"}
};Characteristics of unordered map
- Stores data as key-value pairs
- Duplicate keys: not allowed
- Duplicate values: allowed
- Order: no guaranteed order
- Search: average O(1)
- Insert: average O(1)
- Delete: average O(1)
- Implementation: hash table
Common operations
- insert() – insert a key-value pair
- emplace() – construct and insert an element
- erase() – remove an element
- find() – search for a key
- count() – check whether a key exists
- size() – number of elements
- empty() – check whether the map is empty
- clear() – remove all elements
- operator[] – access or insert a value
- at() – access an existing value
- begin() – iterator to the first element
- end() – iterator to one past the last element
How unordered maps store data
An unordered map uses a hash table internally. When a key is inserted, a hash function computes a hash value, the hash value determines a bucket, and the key-value pair is stored in that bucket. This allows very fast average-case operations.
Accessing elements
Using operator[]:
students[101] = "Alice";
// If the key doesn't exist, it is automatically created.Using at():
cout << students.at(101);
// Unlike operator[], at() throws an exception if the key does not exist.Iterating through an unordered map
for (const auto& student : students)
{
cout << student.first << " : "
<< student.second << endl;
}Note: the iteration order is unspecified and may vary between executions.
Example 1: Creating and displaying an unordered map
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main()
{
unordered_map<int, string> students;
students[101] = "Alice";
students[102] = "Bob";
students[103] = "Charlie";
for (const auto& student : students)
{
cout << student.first << " : ";
cout << student.second << endl;
}
return 0;
}
// Possible output:
// 103 : Charlie
// 101 : Alice
// 102 : Bob
// Note: the order is not guaranteed.Example 2: Searching for a key
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
unordered_map<int, string> students;
students[101] = "Alice";
students[102] = "Bob";
auto it = students.find(102);
if (it != students.end())
{
cout << it->second;
}
else
{
cout << "Student not found";
}
return 0;
}Example 3: Removing an element
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
unordered_map<int, string> products =
{
{1, "Laptop"},
{2, "Mouse"},
{3, "Keyboard"}
};
products.erase(2);
for (const auto& product : products)
{
cout << product.first << " : ";
cout << product.second << endl;
}
return 0;
}Example 4: Word frequency counter
#include <iostream>
#include <unordered_map>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> words =
{
"apple",
"banana",
"apple",
"orange",
"banana",
"apple"
};
unordered_map<string, int> frequency;
for (const string& word : words)
{
frequency[word]++;
}
for (const auto& item : frequency)
{
cout << item.first << " : ";
cout << item.second << endl;
}
return 0;
}Example 5: Employee salary database
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main()
{
unordered_map<int, double> salaries;
salaries[1001] = 55000.50;
salaries[1002] = 62000.75;
salaries[1003] = 48000.00;
cout << "Employee Salaries\n";
for (const auto& employee : salaries)
{
cout << "ID: " << employee.first;
cout << " Salary: ";
cout << employee.second << endl;
}
return 0;
}Common mistakes and pitfalls
- Expecting keys to be sorted. Fix: remember that unordered_map does not maintain key order
- Using duplicate keys expecting multiple entries. Fix: keys must be unique, use std::unordered_multimap if duplicate keys are needed
- Accessing a missing key with operator[] unintentionally. Fix: use find() or at() when you don't want a new key to be created
- Iterating expecting insertion order. Fix: iteration order is unspecified and may change
- Forgetting custom hash functions for user-defined keys. Fix: define a custom hash function when using custom types as keys
Best practices
- Use unordered_map when sorted keys are unnecessary
- Prefer find() for lookups that should not insert new keys
- Use reserve() when inserting a large number of elements to reduce rehashing
- Use emplace() instead of insert() when constructing objects directly in the container
- Pass large unordered maps by const reference to avoid unnecessary copying
- Use custom hash functions for user-defined key types
- Switch to std::map if ordered traversal or range queries are required
When not to use this
- Keys must remain sorted
- You need ordered traversal or range-based queries
- Deterministic iteration order is required
- Worst-case lookup performance is more important than average-case speed
- Memory usage must be minimized, as hash tables generally consume more memory than tree-based maps
Summary
- Unordered Map in C++ stores key-value pairs using a hash table
- Keys are unique, while values may be duplicated
- Keys are not stored in sorted order
- Lookup, insertion, and deletion are typically O(1) on average
- Use operator[], at(), find(), erase(), insert(), emplace(), clear(), and size() for common operations
- unordered_map is ideal for fast key-based lookups when ordering is not required
- Use std::map when sorted keys or ordered traversal are needed
Frequently asked questions
What is the difference between map and unordered_map?
std::map stores keys in sorted order using a self-balancing tree and provides O(log n) operations. std::unordered_map stores keys in a hash table, does not maintain order, and typically provides O(1) average-case operations.
Can an unordered map have duplicate keys?
No. Every key must be unique. If duplicate keys are required, use std::unordered_multimap.
Why is the iteration order different every time?
std::unordered_map organizes elements into hash buckets rather than sorting them. As a result, iteration order is unspecified and may vary across runs or implementations.
What is the difference between operator[] and at()?
operator[] inserts a new element with a default value if the key is missing. at() accesses an existing element and throws an exception if the key does not exist.
When should I use unordered_map instead of map?
Use std::unordered_map when you need fast average-case lookups and do not require sorted keys. Choose std::map when ordered traversal, range queries, or sorted output are important.