Hash Tables and Hash Maps
This chapter implements a hash table (and hash map) in C: hashing, collision resolution, load factor, and rehashing.
Why This Matters
Hash tables give O(1) average lookup, insert, and delete — the fastest general key-value structure. They are the foundation of dictionaries, caches, symbol tables, and countless systems.
Prerequisites
c.ds.2— dynamic arrays.c.memory.3— ownership.
Core Concept
A hash table maps keys to values by:
1. Hashing the key to an integer. 2. Mapping that integer to a bucket index (e.g., hash % capacity). 3. Resolving collisions when two keys map to the same bucket.
Two common collision strategies:
- Chaining: each bucket is a linked list of entries.
- Open addressing: on collision, probe for another slot (linear/quadratic/
double hashing).
The load factor is size / capacity. When it exceeds a threshold, the table rehashes into a larger capacity to keep operations O(1).
Examples
Chaining hash table (string keys)
#include <stdlib.h>
#include <string.h>
typedef struct Entry {
char *key;
int value;
struct Entry *next;
} Entry;
typedef struct {
Entry **buckets;
size_t capacity;
size_t size;
} HashTable;
static size_t hash_str(const char *s, size_t cap)
{
size_t h = 5381;
while (*s) h = h * 33 + (unsigned char)*s++;
return h % cap;
}
The djb2 hash (h = 5381, h = h*33 + c) is simple and effective for strings.
Insert (chaining)
int ht_put(HashTable *t, const char *key, int value)
{
size_t i = hash_str(key, t->capacity);
for (Entry *e = t->buckets[i]; e; e = e->next) {
if (strcmp(e->key, key) == 0) { e->value = value; return 0; }
}
Entry *e = malloc(sizeof *e);
if (!e) return -1;
e->key = strdup(key);
e->value = value;
e->next = t->buckets[i];
t->buckets[i] = e;
t->size++;
return 0;
}
How It Works
A good hash function spreads keys uniformly across buckets, so the average chain/probe length is small. Rehashing to roughly double the capacity when the load factor passes (say) 0.75 keeps operations near O(1).
Variations
Open addressing
Stores entries directly in the array and probes on collision. Better cache locality than chaining, but deletion is trickier (tombstones).
Intrusive hash maps
In kernels/embedded code, entries are embedded in the caller's struct, avoiding separate allocation (like intrusive lists).
Common Mistakes
- A poor hash function (all keys collide → O(n)).
- Forgetting to rehash (load factor grows → O(n)).
- Leaking keys/entries on deletion or destruction.
- Comparing pointers instead of keys.
Undefined Behavior
- Dereferencing a freed entry (use-after-free during delete).
VERIFIED - Reading beyond the bucket array.
VERIFIED
Portability
- Plain C, fully portable.
strdupis POSIX, not ISO C; implement it or
allocate + memcpy.
Under the Hood
A chaining table is an array of pointers; cache behavior depends on the chain length. Open addressing is more cache-friendly. Rehashing is an O(n) operation amortized over inserts.
Practical Usage
- Use hash tables for symbol tables, caches, dictionaries, and deduplication.
- Choose a hash function appropriate to the key type.
- Keep the load factor below ~0.75.
Exercises
1. Implement ht_get, ht_put, ht_del, and ht_free. 2. Add rehashing when the load factor exceeds 0.75. 3. Implement an open-addressing variant with linear probing. 4. Write a small string-to-int dictionary and test it.
Deep Challenge
Implement an open-addressing hash map with robin-hood hashing (minimize probe distance), and explain why it has better cache behavior and lower variance than linear probing.
Related Concepts
c.ds.2— dynamic arrays.c.ds.3— linked lists (for chaining).c.alg.1— hashing.
References
- Standard data-structure literature; "robin hood hashing."
Verification
- Hash-table average O(1) with good hash and load factor.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Hash function
- [ ] Chaining vs. open addressing
- [ ] Load factor and rehashing
- [ ] Insert/get/delete
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.ds.hashtable | 0 | 6 |
| c.ds.hashmap | 0 | 6 |