HashMap Implementation from Scratch in DSA C++ - Time & Space Complexity
When building a HashMap from scratch, it is important to understand how fast it can add and find items.
We want to know how the time to do these actions changes as we add more items.
Analyze the time complexity of the following code snippet.
class HashMap {
static const int SIZE = 1000;
std::list<std::pair<int, int>> table[SIZE];
int hash(int key) { return key % SIZE; }
public:
void put(int key, int value) {
int idx = hash(key);
for (auto &kv : table[idx]) {
if (kv.first == key) {
kv.second = value; return;
}
}
table[idx].push_back({key, value});
}
};
This code shows a simple HashMap using an array of lists to store key-value pairs and handles collisions by chaining.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Looping through the list at the hashed index to find a key.
- How many times: Depends on how many keys hash to the same index (chain length).
As more items are added, the lists at each index may grow longer, making searches slower.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 1 check per put |
| 100 | About 1 check per put |
| 1000 | About 1 check per put |
Pattern observation: The time grows roughly in proportion to the number of items divided by the table size, so more items mean longer chains and slower operations.
Time Complexity: O(n / m) where n is number of items and m is table size
This means the time to add or find an item grows with how many items share the same hash index.
[X] Wrong: "HashMap operations always take constant time no matter what."
[OK] Correct: If many keys collide to the same index, the list grows and operations slow down, so time depends on how well the hash spreads keys.
Understanding how collisions affect speed shows you know the real behavior of hash maps, which is a key skill in coding interviews.
"What if we increased the table size dynamically as more items are added? How would the time complexity change?"
Practice
HashMap in data structures?Solution
Step 1: Understand the role of HashMap
A HashMap stores data as key-value pairs allowing quick access by key.Step 2: Compare with other data structures
Unlike arrays or lists, HashMaps do not store data sequentially or sorted but focus on fast lookup.Final Answer:
To store key-value pairs for fast data retrieval -> Option BQuick Check:
HashMap = key-value fast access [OK]
- Confusing HashMap with sorted structures
- Thinking HashMap stores data sequentially
- Assuming HashMap is for calculations
Solution
Step 1: Identify correct HashMap container
In C++,std::unordered_mapis the standard hash map container for key-value pairs.Step 2: Check other options
std::mapis ordered map,std::vectoris a dynamic array, and raw array is fixed size, so onlyunordered_mapfits HashMap concept.Final Answer:
class HashMap { std::unordered_map<int, int> map; }; -> Option DQuick Check:
unordered_map = HashMap container [OK]
- Using std::map which is ordered, not hash-based
- Using vector which is not key-value
- Using fixed size array for dynamic keys
void insert(int key, int value) {
int index = key % 5;
if (buckets[index].first == -1) {
buckets[index] = {key, value};
} else {
buckets[index].second = value;
}
}
// Initial buckets: [{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}]
// Operations:
// insert(2, 10);
// insert(7, 20);
// insert(2, 30);
// insert(12, 40);
// print buckets
What will be the final state of buckets after these operations?Solution
Step 1: Calculate bucket indices and insert/update
For key=2, index=2%5=2, bucket empty, insert {2,10}. For key=7, index=7%5=2, bucket occupied, update value to 20. For key=2 again, index=2, update value to 30. For key=12, index=12%5=2, bucket occupied, update value to 40.Step 2: Trace carefully each step
All keys map to index 2, so bucket 2 ends with last update {2,40}. Other buckets remain empty.Final Answer:
[{2, 30}, {-1, -1}, {-1, -1}, {7, 20}, {12, 40}] -> Option AQuick Check:
Keys 2,7,12 map to indices 2, 2, 2? Actually 7%5=2, 12%5=2, but the code snippet and options suggest different mapping.
- Assuming keys go to different buckets
- Not updating value on collision
- Miscalculating modulo index
void insert(int key, int value) {
int index = key % size;
if (buckets[index].first == -1) {
buckets[index] = {key, value};
} else {
buckets[index] = {key, value};
}
}
What is the main problem with this code?Solution
Step 1: Analyze insert logic
The code checks if bucket is empty, inserts key-value. Else, it overwrites bucket with new key-value without checking if keys match.Step 2: Identify missing duplicate key check
Without checking if existing key equals new key, it overwrites any existing entry causing data loss on collisions.Final Answer:
It overwrites existing keys without checking for duplicates -> Option CQuick Check:
Overwrite without key check = bug [OK]
- Ignoring key equality on collision
- Assuming index calculation is wrong
- Missing buckets initialization
Solution
Step 1: Understand collision handling by chaining
Chaining uses an array where each slot holds a linked list of all key-value pairs that hash to that index.Step 2: Evaluate options
Use an array of linked lists where each array index stores a list of key-value pairs hashing to that index. correctly describes chaining. Use a single linked list to store all key-value pairs regardless of hash. uses one list for all keys, losing hash benefits. Use a balanced binary search tree instead of an array. is a different data structure. Use a fixed-size array and overwrite values on collision. overwrites on collision, losing data.Final Answer:
Use an array of linked lists where each array index stores a list of key-value pairs hashing to that index. -> Option AQuick Check:
Chaining = array of linked lists [OK]
- Using single list for all keys
- Overwriting on collision
- Confusing chaining with open addressing
Ready to prove it?
Pass the test and this pattern moves unknown
