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, find, or remove items.
We want to know how the time to do these actions changes as the number of stored items grows.
Analyze the time complexity of the following code snippet.
#define SIZE 100
typedef struct Node {
char* key;
int value;
struct Node* next;
} Node;
Node* hashmap[SIZE];
unsigned int hash(char* key) {
unsigned int hash = 0;
for (int i = 0; key[i] != '\0'; i++) {
hash = (hash * 31) + key[i];
}
return hash % SIZE;
}
void put(char* key, int value) {
unsigned int index = hash(key);
Node* newNode = malloc(sizeof(Node));
newNode->key = key;
newNode->value = value;
newNode->next = hashmap[index];
hashmap[index] = newNode;
}
This code adds a key-value pair to a simple HashMap using chaining for collisions.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The loop inside the
hashfunction that processes each character of the key string. - How many times: Once per character in the key, so proportional to key length.
- Secondary operation: Traversing the linked list at
hashmap[index]when searching or inserting (not shown here but relevant for get/remove). - How many times: Depends on number of items in that bucket (collision chain length).
The time to compute the hash grows with the length of the key, which is usually small and constant.
Inserting is fast if the bucket has few items, but if many keys collide, the linked list grows and operations slow down.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | ~10 hash steps + short list traversal (few items) |
| 100 | ~10 hash steps + longer list traversal if collisions increase |
| 1000 | ~10 hash steps + possibly long list traversal if many collisions |
Pattern observation: Hashing cost stays about the same, but collision chains can grow, making operations slower if the table is too small.
Time Complexity: O(k + n/m)
This means time grows with key length k plus average items per bucket n/m, where n is total items and m is bucket count.
[X] Wrong: "HashMap operations always take constant time no matter what."
[OK] Correct: If many keys collide, linked lists grow and operations slow down, so time depends on how well the hash spreads keys.
Understanding how HashMaps work under the hood helps you explain trade-offs and choose the right data structure confidently.
"What if we increased the number of buckets (SIZE) as the number of items grows? How would the time complexity change?"
Practice
hash function in a HashMap implementation?Solution
Step 1: Understand the role of a hash function
A hash function takes a key and converts it into a number (index) that tells where to store the value in the HashMap's internal array.Step 2: Compare other options
Sorting keys or deleting keys are not the job of the hash function. Storing values in a linked list is a collision handling method, not hashing.Final Answer:
To convert a key into an index for storing the value -> Option CQuick Check:
Hash function = key to index [OK]
- Thinking hash function sorts keys
- Confusing hash function with collision handling
- Believing hash function deletes keys
Solution
Step 1: Identify the basic node structure
A HashMap stores key-value pairs. A node withint keyandint valueis a basic unit, soHashNodestruct is correct.Step 2: Check other options
typedef struct { char* key; int value; } HashMap; useschar*key which is not integer keys. typedef struct { int key; int value; } HashMap; names the structHashMapbut it's actually a node. typedef struct { int key; int value; HashNode* next; } HashMap; adds a pointer for chaining but names itHashMap, which is confusing.Final Answer:
typedef struct { int key; int value; } HashNode; -> Option AQuick Check:
Node struct with int key and value = typedef struct { int key; int value; } HashNode; [OK]
- Confusing node and map struct names
- Using wrong key types
- Missing pointer for chaining in basic struct
int hash(int key) {
return key % 5;
}
int index = hash(12);
printf("%d", index);Solution
Step 1: Apply the hash function to key 12
The hash function returnskey % 5. For key 12, 12 % 5 = 2.Step 2: Confirm the printed index
The code prints the index, which is 2.Final Answer:
2 -> Option BQuick Check:
12 % 5 = 2 [OK]
- Calculating 12 % 5 incorrectly
- Confusing modulo with division
- Ignoring hash function logic
void insert(HashNode** table, int size, int key, int value) {
int index = key % size;
HashNode* newNode = malloc(sizeof(HashNode));
newNode->key = key;
newNode->value = value;
newNode->next = NULL;
if (table[index] == NULL) {
table[index] = newNode;
} else {
table[index]->next = newNode;
}
}
What is the main problem in this code?Solution
Step 1: Analyze insertion logic for chaining
If the bucket is empty, it sets the new node. Otherwise, it setstable[index]->next = newNode, which overwrites the existing next pointer, losing the chain.Step 2: Identify correct chaining method
To append, it should traverse to the last node in the chain and then add the new node, not overwrite the first node's next pointer.Final Answer:
It overwrites the existing chain instead of appending correctly -> Option AQuick Check:
Chaining requires appending, not overwriting [OK]
- Ignoring chain traversal
- Assuming single node per bucket
- Not checking for NULL before insertion
Solution
Step 1: Understand necessary components of HashMap
A hash function, chaining with linked lists, and resizing with rehashing are all essential for correct and efficient HashMap behavior.Step 2: Evaluate sorting keys in buckets
Sorting keys in buckets is not required because chaining uses linked lists and search is done by traversing nodes. Sorting adds unnecessary overhead.Final Answer:
Sort the keys in each bucket to speed up search -> Option DQuick Check:
Sorting buckets is unnecessary in chaining [OK]
- Thinking sorting improves chaining search
- Ignoring rehashing on resize
- Skipping collision handling
Ready to prove it?
Pass the test and this pattern moves unknown
