Hash Table, Set, and Map in C++
Hash Table, Set, and Map in C++ Interview with follow-up questions
1. What is a Hash Table in C++ and how does it work?
A hash table is a data structure that maps keys to values using a hash function. It provides average-case O(1) time complexity for insertion, lookup, and deletion.
How it works
- A hash function takes a key and computes an integer index (the hash code).
- The index is mapped into a fixed-size array of buckets:
bucket = hash(key) % num_buckets. - The key-value pair is stored in (or looked up from) that bucket.
- When two different keys hash to the same bucket — a collision — the implementation resolves it using chaining (each bucket holds a linked list) or open addressing (probe for the next available slot).
- When the ratio of elements to buckets (the load factor) becomes too high, the table rehashes: allocates a larger array and reinserts all elements. This keeps the average bucket length short and maintains O(1) amortized performance.
C++ STL implementation: std::unordered_map and std::unordered_set
The STL provides two direct hash table containers:
#include
#include
std::unordered_map word_count;
word_count["hello"] = 1;
word_count["world"] = 2;
// Lookup
auto it = word_count.find("hello");
if (it != word_count.end())
std::cout << it->second; // 1
// Insert or update
word_count["hello"]++;
// Delete
word_count.erase("world");
Key properties of std::unordered_map
- Average O(1) insert, lookup, erase; worst-case O(n) when all keys collide.
- Elements are not stored in any sorted order.
- The default hash is
std::hash, which handles built-in types andstd::string. For custom types you must provide a specialization or a custom hash callable. bucket_count(),load_factor(), andmax_load_factor()expose the internal state.rehash(n)andreserve(n)let you control rehashing proactively.
Custom hash example
struct Point { int x, y; };
struct PointHash {
std::size_t operator()(const Point& p) const {
return std::hash{}(p.x) ^ (std::hash{}(p.y) << 1);
}
};
struct PointEq {
bool operator()(const Point& a, const Point& b) const {
return a.x == b.x && a.y == b.y;
}
};
std::unordered_map map;
When to prefer std::unordered_map over std::map
Use std::unordered_map when you need fast average-case access and do not care about key ordering. Use std::map when you need keys in sorted order or require ordered iteration.
Follow-up 1
Can you explain a scenario where a hash table would be more efficient than other data structures?
A hash table is more efficient than other data structures in scenarios where fast insertion, deletion, and retrieval of elements is required. For example, consider a scenario where you need to store a large number of key-value pairs and frequently perform operations like searching for a specific key or updating the value associated with a key. In such cases, a hash table can provide constant-time average-case performance for these operations, making it more efficient than other data structures like arrays or linked lists.
Follow-up 2
What is a hash function and how does it relate to a hash table?
A hash function is a function that takes an input (such as a key) and produces a fixed-size numerical value called a hash code. The hash code is used as an index to determine the bucket where a key-value pair should be stored in a hash table. The quality of a hash function is important for the performance of a hash table. A good hash function should distribute the hash codes uniformly across the available buckets, minimizing collisions and ensuring efficient retrieval of key-value pairs. In C++, the std::hash function template is commonly used to generate hash codes for built-in types, and custom hash functions can be defined for user-defined types.
Follow-up 3
How do you handle collisions in a hash table?
Collisions occur when two or more keys produce the same hash code, resulting in multiple key-value pairs being stored in the same bucket. There are different techniques to handle collisions in a hash table. Two common approaches are:
Separate Chaining: In this approach, each bucket contains a linked list of key-value pairs. When a collision occurs, the new key-value pair is appended to the linked list in the corresponding bucket. When retrieving a key-value pair, the hash code is used to locate the bucket, and then a search is performed within the linked list to find the desired pair.
Open Addressing: In this approach, when a collision occurs, the hash table probes for an alternative empty bucket to store the key-value pair. Different probing techniques can be used, such as linear probing (checking the next bucket), quadratic probing (checking buckets with quadratic offsets), or double hashing (using a second hash function to calculate the next bucket).
Follow-up 4
What is the time complexity of operations in a hash table?
The time complexity of operations in a hash table depends on the specific implementation and the number of elements in the hash table. In the average case, assuming a good hash function and a low number of collisions, the time complexity of insertion, deletion, and retrieval operations is O(1), i.e., constant time. This means that the time taken to perform these operations does not depend on the size of the hash table. However, in the worst case, when there are many collisions or a poor hash function is used, the time complexity can degrade to O(n), where n is the number of elements in the hash table. It is important to note that the worst-case scenario is rare and can be mitigated by using appropriate hash functions and collision resolution techniques.
2. What is a Set in C++ and what are its uses?
A set is an associative container that stores unique elements — no duplicates are allowed. C++ provides two set variants with different underlying implementations and performance characteristics.
std::set — ordered set
Implemented as a balanced binary search tree (typically a red-black tree). Elements are stored in ascending sorted order by default.
#include
std::set s = {5, 3, 1, 4, 2};
// Stored as: 1 2 3 4 5
s.insert(3); // no-op; 3 already exists
s.erase(4);
bool found = s.count(3); // 1 (found) or 0 (not found)
auto it = s.find(2); // iterator or s.end()
Time complexities for std::set: O(log n) for insert, find, and erase.
std::unordered_set — hash-based set
Implemented as a hash table. Elements are not stored in any particular order but average-case operations are O(1).
#include
std::unordered_set words = {"apple", "banana", "cherry"};
words.insert("date");
bool has_apple = words.count("apple"); // 1
words.erase("banana");
Time complexities: average O(1) for insert, find, erase; worst-case O(n) on hash collisions.
std::multiset — ordered set allowing duplicates
Allows multiple elements with the same value, stored in sorted order. Use count(key) to find how many copies exist.
Common uses of sets
- Membership testing: Quickly check whether an element exists without iterating.
- Duplicate removal: Insert a range into a set; only unique elements survive.
- Sorted unique sequences: When you need elements in order without duplicates,
std::setis direct. - Set operations: Intersection, union, and difference are straightforward with
std::set_intersection,std::set_union, andstd::set_differencefrom `` (they require sorted ranges).
Choosing between std::set and std::unordered_set
std::set |
std::unordered_set |
|
|---|---|---|
| Order | Sorted | Unspecified |
| Lookup | O(log n) | O(1) average |
| Key requirement | Requires < operator |
Requires std::hash and == |
| Iterator invalidation | Never on insert/erase (other than the erased element) | On rehash |
Prefer std::unordered_set when you only need membership testing and order does not matter. Prefer std::set when you need sorted iteration, range queries (lower_bound, upper_bound), or when key hashing is not straightforward.
Follow-up 1
What is the difference between a set and a multiset in C++?
The main difference between a Set and a Multiset in C++ is that a Set only stores unique elements, while a Multiset allows duplicate elements. In a Set, each element can only appear once, whereas in a Multiset, multiple elements with the same value can be stored. Another difference is that Sets are implemented using a balanced binary search tree or a hash table, while Multisets are typically implemented using a binary search tree.
Follow-up 2
How do you insert and remove elements in a set?
In C++, you can insert elements into a Set using the insert() function. The insert() function takes the value of the element to be inserted as its argument. If the element is already present in the Set, it will not be inserted again. To remove elements from a Set, you can use the erase() function. The erase() function takes either the value of the element or an iterator pointing to the element to be removed.
Follow-up 3
What is the time complexity of operations in a set?
The time complexity of operations in a Set depends on the implementation. If a Set is implemented using a balanced binary search tree, the time complexity of operations like insertion, deletion, and search is O(log n), where n is the number of elements in the Set. If a Set is implemented using a hash table, the average time complexity of these operations is O(1), but in the worst case, it can be O(n).
Follow-up 4
Can you give an example of a real-world scenario where a set would be useful?
Sure! One example of a real-world scenario where a Set would be useful is in a social networking application. Let's say you have a database of users and you want to find all the unique interests among them. You can use a Set to store the interests of each user, and then iterate over the Sets to find the unique interests. This can be useful for targeted advertising, recommending similar interests to users, or analyzing user preferences.
3. What is a Map in C++ and how is it different from a Hash Table?
In C++, a map (std::map) is an ordered associative container that stores key-value pairs with unique keys. A hash table (std::unordered_map) stores key-value pairs with no ordering guarantee, using a hash function to locate elements.
std::map — ordered map
- Implemented as a balanced binary search tree (red-black tree internally).
- Keys are stored in sorted ascending order (or by a custom comparator).
- All operations — insert, find, erase — are O(log n).
- Iterating over a
std::mapyields elements in key order. - Keys must support
operator<(or a custom comparator); no hash function is needed.
#include
std::map scores;
scores["Alice"] = 95;
scores["Bob"] = 87;
scores["Carol"] = 92;
// Iterates in alphabetical order: Alice, Bob, Carol
for (auto& [name, score] : scores)
std::cout << name << ": " << score << '\n';
auto it = scores.find("Bob"); // O(log n)
scores.erase("Carol");
// Range queries unique to ordered containers:
auto low = scores.lower_bound("B"); // first key >= "B"
std::unordered_map — hash-based map
- Implemented as a hash table with chaining.
- No ordering of keys — iteration order is unspecified and may change after a rehash.
- Average O(1) for insert, find, erase; worst-case O(n) if many keys collide.
- Keys must be hashable (
std::hashmust exist, or a custom hash must be provided) and supportoperator==.
#include
std::unordered_map scores;
scores["Alice"] = 95;
scores["Bob"] = 87;
auto it = scores.find("Alice"); // average O(1)
Comparison
| Property | std::map |
std::unordered_map |
|---|---|---|
| Underlying structure | Red-black tree | Hash table |
| Key ordering | Sorted (ascending) | Unspecified |
| Insert / find / erase | O(log n) | O(1) average, O(n) worst |
| Range queries | Yes (lower_bound, upper_bound) |
No |
| Key requirement | operator< |
std::hash + operator== |
| Memory overhead | Per-node tree pointers | Bucket array + node pointers |
| Iterator invalidation on insert | Never | Only on rehash |
When to choose each
- Use
std::mapwhen you need keys in sorted order, range queries (e.g., "all entries between keys A and B"), or when hashing the key type is impractical. - Use
std::unordered_mapwhen you need the fastest average-case access and do not require ordering. It is the default choice for most key-value lookup tables. - Use
std::mapin security-sensitive contexts where an adversary could craft keys to maximize hash collisions and degradestd::unordered_mapto O(n) per operation.
Follow-up 1
What is the difference between a map and a multimap in C++?
In C++, a map allows only one value per key, while a multimap allows multiple values per key. This means that in a map, each key is associated with a unique value, whereas in a multimap, each key can be associated with multiple values. The keys in both map and multimap are sorted in ascending order by default.
Follow-up 2
How do you insert and remove elements in a map?
To insert an element into a map, you can use the insert function or the subscript operator []. The insert function takes a pair of key-value as its argument, while the subscript operator allows you to directly assign a value to a key. To remove an element from a map, you can use the erase function, which takes the key of the element to be removed as its argument.
Follow-up 3
What is the time complexity of operations in a map?
The time complexity of operations in a map depends on the implementation. In general, the average time complexity for insertion, deletion, and search operations in a map is O(log n), where n is the number of elements in the map. However, the worst-case time complexity for these operations can be O(n) if the map is not balanced. It is important to note that the time complexity for operations in a hash table is usually O(1) on average, making it more efficient for large datasets.
Follow-up 4
Can you give an example of a real-world scenario where a map would be useful?
A map can be useful in various real-world scenarios where you need to associate a value with a unique key. For example, in a phone book application, you can use a map to store the names and phone numbers of contacts, where the names are the keys and the phone numbers are the values. This allows you to quickly retrieve the phone number of a contact by searching for their name. Another example is a dictionary application, where you can use a map to store words and their definitions, with the words as keys and the definitions as values.
4. How does C++ handle collisions in a Hash Table?
A collision occurs when two distinct keys hash to the same bucket. C++ hash table implementations (and the STL's std::unordered_map / std::unordered_set) handle collisions primarily through separate chaining, while other strategies like open addressing are used in custom or external implementations.
1. Separate chaining (used by std::unordered_map)
Each bucket holds a linked list (or another container) of all key-value pairs that hash to that bucket. On a lookup, the implementation computes the bucket index and then walks the list to find the exact key using operator==.
bucket 0: [(key_a, val_a)]
bucket 1: [(key_b, val_b) -> (key_c, val_c)] // collision: both hash to 1
bucket 2: []
bucket 3: [(key_d, val_d)]
- Insert: O(1) amortized (append to list).
- Lookup/erase: O(1) average, O(n) worst case (all keys in one bucket).
- The STL controls the load factor (default max 1.0) and rehashes automatically to keep chains short.
2. Open addressing
All elements are stored directly in the array. On a collision, the implementation probes for the next available slot according to a probing sequence. The C++ standard library does not mandate open addressing, but many high-performance custom hash maps (e.g., Google's absl::flat_hash_map, ankerl::unordered_dense) use it because it is more cache-friendly than chaining.
Common probing strategies:
- Linear probing: check
(h + 1) % N,(h + 2) % N, … Simple, but causes primary clustering — runs of filled slots form and grow, degrading performance. - Quadratic probing: check
(h + 1²) % N,(h + 2²) % N, … Reduces primary clustering but may not probe every bucket (requires table size to be a prime or a power of two). - Double hashing: use a second hash function to determine the step size —
(h + i * h2(key)) % N. Minimizes clustering but requires a carefully chosen second hash.
3. Load factor and rehashing
Both strategies degrade as the table fills. The load factor α = n/N (elements/buckets) controls this:
std::unordered_maprehashes automatically whenload_factor() > max_load_factor()(default 1.0).- After rehashing, all elements are redistributed into a larger bucket array, restoring O(1) average performance.
- You can control this explicitly:
std::unordered_map m;
m.reserve(1000); // pre-allocate for ~1000 elements without rehashing
m.max_load_factor(0.7); // rehash sooner to reduce collisions
Interview gotcha: A pathological hash function (or adversarially crafted keys) can force all keys into the same bucket, degrading every operation to O(n). Production implementations use randomized seeds (e.g., per-process seed in libstdc++ and MSVC) to make collision attacks impractical.
Follow-up 1
What is open addressing and chaining in the context of hash table collisions?
Open addressing is a collision handling method where the colliding elements are stored in the next available slot in the hash table. In this method, the hash table is probed sequentially until an empty slot is found. Chaining, on the other hand, is a method where each slot in the hash table contains a linked list. Colliding elements are stored in this linked list, allowing multiple elements to be stored in the same slot.
Follow-up 2
What are the pros and cons of each collision handling method?
The pros of open addressing are that it has a smaller memory overhead and can provide better cache performance. However, it can suffer from clustering, where consecutive elements are placed in nearby slots, leading to longer search times. Chaining, on the other hand, avoids clustering and allows for an unlimited number of elements to be stored in the same slot. However, it has a larger memory overhead and can have slower cache performance compared to open addressing.
Follow-up 3
Can you write a simple code snippet to illustrate how collisions are handled in a hash table?
Sure! Here's an example of how collisions can be handled using chaining in C++:
#include
#include
#include
class HashTable {
private:
std::vector> table;
int size;
public:
HashTable(int size) : size(size) {
table.resize(size);
}
void insert(int key) {
int index = key % size;
table[index].push_back(key);
}
void print() {
for (int i = 0; i < size; i++) {
std::cout << "Slot " << i << ": ";
for (int key : table[i]) {
std::cout << key << " -> ";
}
std::cout << "NULL" << std::endl;
}
}
};
int main() {
HashTable hashTable(10);
hashTable.insert(5);
hashTable.insert(15);
hashTable.insert(25);
hashTable.insert(35);
hashTable.insert(45);
hashTable.print();
return 0;
}
5. What are the differences between a Hash Table, Set, and Map in C++?
Hash tables, sets, and maps are related associative containers that each serve a distinct purpose. In C++, each concept has both an ordered and an unordered (hash-based) STL implementation.
Hash Table
A hash table maps keys to values using a hash function for O(1) average-case access. It stores key-value pairs and does not maintain any ordering of keys.
- STL type:
std::unordered_map(key → value),std::unordered_set(keys only) - Use when you need the fastest average lookup by key and ordering does not matter.
Set
A set stores unique keys with no associated values. Its purpose is membership testing and duplicate elimination, not key-value association.
- Ordered:
std::set— red-black tree; O(log n); keys in sorted order. - Unordered:
std::unordered_set— hash table; O(1) average; no ordering. - Use when you need to track which elements exist, remove duplicates, or perform set operations (union, intersection, difference).
Map
A map stores unique key-value pairs, associating each key with exactly one value. The distinction from a raw hash table is largely conceptual in C++ — the ordered vs. unordered split matters more in practice.
- Ordered:
std::map— red-black tree; O(log n); keys in sorted order; supports range queries. - Unordered:
std::unordered_map— hash table; O(1) average; keys in unspecified order. - Use when you need to look up, insert, or update values by key.
Side-by-side comparison
std::set |
std::unordered_set |
std::map |
std::unordered_map |
|
|---|---|---|---|---|
| Stores | Keys only | Keys only | Key-value pairs | Key-value pairs |
| Duplicates | No | No | No (by key) | No (by key) |
| Ordering | Sorted | None | Sorted by key | None |
| Lookup | O(log n) | O(1) avg | O(log n) | O(1) avg |
| Underlying structure | Red-black tree | Hash table | Red-black tree | Hash table |
| Key requirement | operator< |
std::hash + == |
operator< |
std::hash + == |
Common source of confusion in interviews
The original answer stated that std::unordered_map provides an implementation of a Map and of a Set — that is misleading. std::unordered_map is specifically a key-value hash table; std::unordered_set is the hash-based set. They are separate containers.
Decision guide
- Need to check membership fast? →
std::unordered_set - Need unique sorted keys? →
std::set - Need key → value lookup, unordered? →
std::unordered_map - Need key → value lookup, ordered or with range queries? →
std::map
Follow-up 1
Can you explain the underlying data structures used for each?
The underlying data structures used for Hash Table, Set, and Map in C++ are typically based on hash tables.
Hash Table: A Hash Table uses an array of buckets, where each bucket can store multiple elements. Each element is stored at a specific index in the array based on its hash value. In case of collisions, where multiple elements have the same hash value, separate chaining or open addressing techniques are used to handle them.
Set: The underlying data structure for a Set is similar to that of a Hash Table. It uses an array of buckets, where each bucket can store multiple elements. However, in a Set, only the keys are stored, and the values are ignored.
Map: The underlying data structure for a Map is also similar to that of a Hash Table. It uses an array of buckets, where each bucket can store multiple key-value pairs. Both the keys and values are stored in a Map.
Follow-up 2
How does the choice of these data structures affect the performance of operations?
The choice of data structure can significantly affect the performance of operations on Hash Table, Set, and Map in C++.
Hash Table: A Hash Table provides fast insertion, deletion, and retrieval of elements based on their keys. The average time complexity for these operations is O(1), assuming a good hash function and a low collision rate. However, in the worst case, the time complexity can be O(n) if all elements have the same hash value and are stored in a single bucket.
Set: The performance of operations on a Set is similar to that of a Hash Table. The average time complexity for insertion, deletion, and retrieval is O(1), assuming a good hash function and a low collision rate. In the worst case, the time complexity can be O(n) if all elements have the same hash value and are stored in a single bucket.
Map: The performance of operations on a Map is also similar to that of a Hash Table. The average time complexity for insertion, deletion, and retrieval is O(1), assuming a good hash function and a low collision rate. In the worst case, the time complexity can be O(n) if all elements have the same hash value and are stored in a single bucket.
Follow-up 3
Can you give an example where one would be preferred over the others?
The choice between a Hash Table, Set, and Map depends on the specific requirements of the problem at hand.
Hash Table: A Hash Table is preferred when there is a need to store key-value pairs and efficient retrieval of values based on keys is required. For example, a Hash Table can be used to implement a dictionary, where words are stored as keys and their meanings as values.
Set: A Set is preferred when there is a need to store a collection of unique elements and efficient membership testing is required. For example, a Set can be used to store a list of unique usernames in a social media application.
Map: A Map is preferred when there is a need to store key-value pairs and efficient retrieval of both keys and values is required. For example, a Map can be used to store student records, where student IDs are stored as keys and corresponding information such as name and grade are stored as values.
Live mock interview
Mock interview: Hash Table, Set, and Map in C++
- Read your scene and goals
- Talk it out; goals tick off live
- Get a score and stronger lines
Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.