Hash Map
A key → value store backed by a hash table with expected O(1) get, put, and delete.
Definition
A hash map is the key → value interface of a Hash Table. put(k, v) associates a value with a key, get(k) retrieves it, remove(k) deletes it — all in expected O(1).
It is the most used data structure in interview solutions: counting frequencies, indexing elements by value, memoizing subproblems, mapping ids to objects, and building adjacency lists all reduce to a hash map.
Language implementations: Python dict (insertion-ordered, open addressing), Java HashMap (chaining with treeified bins), C++ std::unordered_map (chaining), JavaScript Map (insertion-ordered), Go map (bucketed open addressing).
Intuition
A mental model before the formal terms.
A phone book where instead of flipping to a page, you compute the page number from the name. The book is unordered and has some blank pages, but every name is exactly where the computation says it is.
Under the hood it is the coat check from Hash Table: the ticket is the key, the coat is the value.
How it works
- Hash the key to a bucket index.
put: search the bucket for an equal key; overwrite its value if found, else append a new(key, value)entry and grow the table if the load factor is exceeded.get: search the bucket for an equal key; return its value or a sentinel (None,null,undefined).remove: search and unlink the entry.- Convenience operations are built on these:
getOrDefault,computeIfAbsent,counter[k] += 1,setdefault.
Why it works
Inherits the O(1) expected bound of the underlying Hash Table: a bounded load factor keeps buckets short and uniform hashing keeps them balanced.
Keys are compared with equals after matching hashes, so distinct keys with equal hashes are still distinguished.
Operations
| Operation | Description | Cost |
|---|---|---|
| put(k, v) / set | Insert or overwrite. | O(1) average |
| get(k) | Retrieve the value or a sentinel. | O(1) average |
| remove(k) / delete | Remove the entry. | O(1) average |
| containsKey(k) / has | Membership test. | O(1) average |
| keys() / values() / entries() | Iterate all stored data. | O(n) |
| size() | Number of entries. | O(1) |
Recognition
How to tell a problem wants this.
- "Count how many times…", "find the first/most frequent…", "is there a pair such that…".
- You need to remember something about each element you have already seen while scanning once (index, count, last position).
- Memoization of a recursive function keyed by its arguments.
- Grouping items by a computed key (anagrams, same remainder, same length).
Interactive demo
Play, step, change the input. ← → and space work too.
Showing the closely related Hash Table visualization.
1h = key % m2insert: scan bucket[h]; if key present: done, else append to the chain3search: scan bucket[h] comparing each key4delete: scan bucket[h]; unlink the matching nodePseudocode
1class HashMap:2 table = HashTable()3 put(k, v): table.put(k, v)4 get(k): return table.get(k) or null5 remove(k): table.remove(k)6 getOrDefault(k, d): v = get(k); return v if v != null else dImplementation
1from collections import Counter, defaultdict2 3 4# dict is the built-in hash map. This shows the core API.5def demo() -> None:61 · Create and insert7 ages: dict[str, int] = {}8 ages["alice"] = 30 # insert or overwrite9 ages.setdefault("bob", 25) # insert only if absent10 ages["bob"] = 26 # overwrite11 122 · Lookup without inserting13 if "carol" not in ages:14 print("carol missing")15 bob_age = ages.get("bob", -1)16 173 · Counting pattern18 words = ["a", "b", "a"]19 freq: defaultdict[str, int] = defaultdict(int)20 for w in words:21 freq[w] += 122 counts = Counter(words) # same thing, batteries included23 244 · Erase and iterate25 del ages["alice"]26 for name, age in ages.items(): # insertion order (3.7+)27 print(name, age)28 print("size", len(ages), "bob", bob_age, dict(freq), counts.most_common(1))29 30 31demo()dict[str, int]annotates the map;ages["alice"] = 30inserts or overwrites.setdefaultinserts only when absent and returns the value;get(key, default)reads without inserting."carol" not in agesis the membership test — O(1).defaultdict(int)makesfreq[w] += 1work without a check;Counteris a specialised dict for counting.del ages["alice"]raises KeyError if missing;ages.pop("alice", None)does not. Iteration is insertion-ordered.
- Dict preserves insertion order since 3.7 —
OrderedDictis only needed formove_to_end. - Dict comprehensions
{k: v for ...}anddict(zip(keys, values))build maps concisely. - Keys must be hashable; use
tupleinstead oflistfor composite keys.
- Iterating
for k in dand deleting inside the loop (RuntimeError: dictionary changed size). - Using
defaultdictand then checkingkey in dafter an accidental read created the key. - Passing a mutable default like
{}as a function argument default.
- Insertion order: Python dict (3.7+) and JS/TS Map are ordered; C++ unordered_map is not.
- Missing-key access: C++
operator[]inserts a default; Pythond[k]raises KeyError; JSgetreturns undefined. - JS objects stringify keys and expose prototype properties; Map, dict and unordered_map keep key types.
- Sorted alternative: C++
std::map, Pythonsortedcontainers(third-party), JS none built in.
Complexity
| Operation | Average | Worst | Note |
|---|---|---|---|
| Access | O(1) | O(n) | By key. |
| Search | O(1) | O(n) | By key; searching by value is O(n). |
| Insert | O(1) | O(n) | Amortized; resize is O(n). |
| Delete | O(1) | O(n) | |
| Update | O(1) | O(n) | |
| Iterate | O(n) | O(n + m) | |
| Space | O(n) | ||
Advantages & disadvantages
- Expected constant-time operations for any hashable key.
- Extremely versatile: counters, indexes, caches, graphs, and memo tables are all one-liners.
- Insertion-ordered in Python and JavaScript, which often removes the need for a separate ordering structure.
- No sorted order or range queries.
- Memory-heavy compared with arrays: boxed keys/values, hash storage, empty slots.
- Worst-case
O(n)operations under pathological hashing; unpredictable resize pauses.
Use cases
- Frequency counting and anagram grouping.
- Two-sum style complement lookup and prefix-sum → count maps.
- Memoization tables for Dynamic Programming and Memoization (Top-Down DP).
- Adjacency lists keyed by node id, id → object registries, configuration lookups.
- Any lookup by key where order does not matter.
- Counting, indexing, grouping, and caching during a single pass.
- Memoizing recursive calls keyed by argument tuple.
Alternatives
Common mistakes
- Checking
if map[key]instead ofkey in mapwhen the stored value may be falsy (0,"",false). - Using plain JS objects as maps with non-string keys (they are coerced to strings) — use
Map. - Mutating a key object after insertion.
- Relying on iteration order in Java
HashMap, C++unordered_map, or Gomap(Go randomises it on purpose). - In Two Sum, inserting the current element before checking for its complement, so
target/2matches itself.
Interview patterns
- Complement lookup: Two Sum, Subarray Sum Equals K (prefix sum → count).
- Frequency map + heap: Top K Frequent Elements.
- Grouping by canonical key: Group Anagrams.
- Index map for O(1) delete in an array: Insert Delete GetRandom O(1).
- Map + doubly linked list: LRU Cache.
- Recognizing the approach from an array and a targetIntermediate
- Deciding whether O(n²) can be improvedIntermediate
- Hash map or array?Beginner
- Average case versus worst caseIntermediate
- Minimum Size Subarray SumIntermediate
- Two SumBeginner
- Longest Substring Without Repeating CharactersIntermediate