Design a HashMap from Scratch
Problem Implement a hash map from scratch — an array of buckets with collisions resolved by chaining — supporting put, get, and remove with average-case O(1).
Requirements
put(key, value) -> void— insert or overwrite an existing keyget(key) -> value?— null/absent on missremove(key) -> bool— whether a key was presentsize() -> intandcontainsKey(key) -> bool- Average O(1) for all three core operations under a bounded load factor
Core design
- Backing array of N buckets.
index = hash(key) % N(in practicehash & (N-1)with a power-of-two N, which is why real implementations size that way). - Each bucket is a chain — a linked list or dynamic array of
Entry(key, value, next). On collision, append to that bucket; lookup walks the chain comparing keys withequals, not just the hash. - Hash and equality must agree: two keys that are equal must hash identically, or the map loses entries. Mutable keys mutated after insertion are unfindable — worth stating.
- Load factor = size / N. Once it exceeds a threshold (0.75 is the usual choice), resize: allocate a larger array, rehash every entry into it. Resizing is O(n) but rare, so put stays O(1) amortized.
- Without resizing, chains grow linearly and every operation degrades toward O(n) — resizing is what keeps the O(1) claim honest.
Discussion points
- Chaining vs. open addressing (linear probing): chaining is simple and degrades gracefully; open addressing has better cache locality and no per-entry node, but needs tombstones on delete and suffers clustering. Name the trade-off explicitly.
- Adversarial or poor hash functions collapse everything into one bucket, making lookups O(n). Mitigations: hash mixing/spreading, and converting an over-long chain into a balanced tree (O(log n) worst case) as modern JDK HashMap does.
- Contrast with an ordered map backed by a balanced BST: O(log n) operations but sorted iteration and range queries. Hash maps buy average O(1) by giving up all ordering — the iteration order is not merely unspecified, it can change on resize.
- Edge cases: null keys, overwriting an existing key (size must not increment), and removing the head of a chain.
- Concurrency: read-during-resize can see a half-migrated table. Discuss a global lock, lock striping, or a copy-on-write table.
- Extension: incremental/rehash-on-access resizing to avoid the latency spike of a stop-the-world rehash on a large table.
asked …