The hash map is the most useful data structure in interviews. The core move is always the same: remember what you’ve seen so you can look it up in O(1) — turning quadratic searches into linear ones.
The idea
The classic Two Sum problem: find two numbers that add to a target. The brute force checks every pair — O(n²). The hash-map solution walks the array once, and for each element asks the map a single O(1) question: “have I already seen the complement target − current?” If yes, we have our pair. We store each number as we go.
The template: Two Sum
def two_sum(nums, target):
seen = {} # value -> index
for i, n in enumerate(nums):
if target - n in seen: # O(1): have I seen the complement?
return [seen[target - n], i]
seen[n] = i
return []
# two_sum([2,7,11,15], 9) -> [0, 1]O(n) time, O(n) space. This single idea — store seen values, look up the complement — solves a large family of “find two things that relate” problems.
Frequency counting and grouping
Hash maps are also how you count and group. Python’s Counter is a hash map specialised for counting:
from collections import Counter, defaultdict
# group anagrams: words that share the same sorted letters
def group_anagrams(words):
groups = defaultdict(list)
for w in words:
key = "".join(sorted(w)) # anagrams → same key
groups[key].append(w)
return list(groups.values())
# group_anagrams(["eat","tea","tan","ate","nat","bat"])
# -> [["eat","tea","ate"], ["tan","nat"], ["bat"]]The pattern: choose a key that collapses “equivalent” items together (sorted letters for anagrams, a canonical form for duplicates), and bucket by it.
Worked example: subarray sum equals K (map + prefix sum)
Hash maps combine beautifully with prefix sums (Lesson 7) to count subarrays with a target sum in O(n). Keep a running sum and a map of how many times each running-sum value has occurred; a subarray sums to k whenever running − k has been seen:
def subarray_sum(nums, k):
count = 0; running = 0
seen = {0: 1} # a prefix sum of 0 seen once (empty prefix)
for n in nums:
running += n
count += seen.get(running - k, 0) # how many earlier prefixes make a k-sum?
seen[running] = seen.get(running, 0) + 1
return count
# subarray_sum([1,1,1], 2) -> 2Step-by-step walkthrough
Trace Two Sum on nums = [3, 2, 4, 7], target = 6. For each number we ask the map one question — have I already seen the complement? — then remember the current number.
| i | nums[i] | need = target − x | need in map? | map {value: index} so far |
|---|---|---|---|---|
| 0 | 3 | 3 | no | {} |
| 1 | 2 | 4 | no | {3:0} |
| 2 | 4 | 2 | yes → answer | {3:0, 2:1} |
At i = 2 the complement 2 is already in the map at index 1, so the answer is [1, 2] — one pass, O(n), instead of checking every pair.
When to reach for it
Signals: you need to ask “have I seen …?” or “how many times …?” quickly; you catch yourself writing a nested loop that re-scans the array for each element; you need to group or de-duplicate; or you need O(1) lookups by key. Hash maps also underpin more complex structures — an LRU cache is a hash map plus a doubly-linked list.
Pitfalls
- Storing before checking (or vice-versa). In Two Sum, check the complement before inserting the current element, or you may match an element with itself.
- Unhashable keys. Lists/dicts can’t be dict keys; convert to a tuple or string (e.g.
tuple(sorted(...))). - Memory. The map is O(n) space. If the interviewer wants O(1) space and the array is sorted, use two pointers (Lesson 3) instead.
Recap
A hash map answers “seen it?” and “how many?” in O(1), turning O(n²) searches into O(n) at the cost of O(n) space. The Two-Sum insight (look up the complement), frequency counting (Counter), grouping (bucket by a canonical key), and map + prefix sum (count subarrays) cover a huge range of questions. Signal: re-scanning the array for each element, or count/group/seen.
Practice: 13 solved problems on hash maps → — cover each one, solve it yourself, then check the tested code.
Next: Lesson 7 — Prefix Sums, for O(1) range queries.