Beginner: foundations · Hashing, two pointers and sliding window · topic 11 of 61 · 35 min
Hash maps and hash sets
Explanation
Why hashing
Many problems ask the same small question again and again: "have I seen this before?" or "what do I know about this key?". With a list, each check scans the list, O(n), so checking every item costs O(n²). A hash map answers in O(1) on average, which turns many O(n²) solutions into O(n). That one swap is the most common speed-up in coding interviews.
A hash map stores key → value pairs, like roll number → student name. A hash set stores only keys and answers one question: is this key present? Both are built the same way, on a structure called a hash table.
The hash function, in plain words
A hash function turns a key into a whole number, called its hash. It must always give the same number for the same key, and it should spread different keys over many different numbers. The table keeps an array of slots called buckets, and a key goes to bucket hash(key) % number_of_buckets. Finding a key means: compute its hash, jump to that bucket, and look only there.
class TinyHashMap:
def __init__(self, size=8):
self.buckets = [[] for _ in range(size)]
def _bucket(self, key):
return self.buckets[hash(key) % len(self.buckets)]
def put(self, key, value):
bucket = self._bucket(key)
for pair in bucket:
if pair[0] == key:
pair[1] = value # key already there: update
return
bucket.append([key, value])
def get(self, key):
for k, v in self._bucket(key):
if k == key:
return v
return None
m = TinyHashMap()
m.put(3, "three")
m.put(11, "eleven") # 11 % 8 == 3: same bucket as 3, a collision
print(m.buckets[3]) # Output: [[3, 'three'], [11, 'eleven']]
print(m.get(11)) # Output: elevenEqual keys always have equal hashes. String hashes in Python are randomised each time the program starts (a protection against attacks), so never store hash("abc") and expect the same number in the next run.
Collisions: chaining and open addressing
A collision is two different keys landing in the same bucket. It cannot be avoided, because there are far more possible keys than buckets. There are two main fixes:
- Chaining: each bucket holds a small list of (key, value) pairs, as in the toy above; a lookup scans only that short list. Java's HashMap works this way (and turns very long chains into small trees).
- Open addressing: each slot holds at most one pair. If the slot is taken, the table probes other slots in a fixed order until it finds the key or an empty slot. Python's dict and set use open addressing.
To keep chains and probe sequences short, the table grows when it gets too full; the ratio of items to slots is called the load factor. Growing means making a bigger array and re-inserting every key, O(n) once, but it happens rarely, so inserting is O(1) amortised, like list.append in Module 1.
Average O(1), worst O(n)
| Operation | Python | JavaScript | Average | Worst |
|---|---|---|---|---|
| Insert or update | d[k] = v | m.set(k, v) | O(1) | O(n) |
| Lookup | d[k], d.get(k) | m.get(k) | O(1) | O(n) |
| Membership | k in d, x in s | m.has(k), st.has(x) | O(1) | O(n) |
| Delete | del d[k], s.discard(x) | m.delete(k) | O(1) | O(n) |
| Visit every key | for k in d | for (const [k, v] of m) | O(n) | O(n) |
The worst case happens when many keys collide, for example with a deliberately bad hash function. With built-in types it is very rare, and interviewers expect you to say "O(1) on average". The space cost of a map or set with n keys is O(n).
Which keys are allowed
A key must be hashable: its hash must never change while it sits in the table. Immutable built-ins are hashable: int, float, str, bool, and tuples whose items are all hashable. Lists, dicts and sets can change, so they are not: d[[1, 2]] = "x" raises a TypeError that says unhashable type: 'list'. To use a list or a pair of coordinates as a key, turn it into a tuple.
visited = set()
visited.add((2, 3)) # a grid cell (row, col) as a tuple
print((2, 3) in visited) # Output: True
counts = {}
key = tuple([1, 2])
counts[key] = counts.get(key, 0) + 1
print(counts) # Output: {(1, 2): 1}In JavaScript use Map and Set. A plain object turns every key into a string, so o[1] and o["1"] are the same key. Map keeps keys as they are, but arrays and objects are compared by identity: two separate [1, 2] arrays are two different keys. Use a string such as "1,2" as the key instead.
const seen = new Map();
seen.set('UPI', 3);
console.log(seen.get('UPI'), seen.has('card'), seen.size); // 3 false 1
const o = {};
o[1] = 'a';
o['1'] = 'b';
console.log(Object.keys(o)); // [ '1' ] (one key: 1 became "1")Two-sum with a map
Given nums and a target, find two different positions whose values add up to the target. Brute force tries every pair, O(n²). With a map from value to index, walk once: for each x the partner you need is target − x. If the partner is already in the map, you are done; otherwise store x with its index. Check BEFORE storing, so an item is never paired with itself. O(n) time and O(n) extra space.
def two_sum(nums, target):
seen = {} # value -> index
for i, x in enumerate(nums):
need = target - x
if need in seen:
return [seen[need], i]
seen[x] = i
return []
print(two_sum([2, 7, 11, 15], 9)) # Output: [0, 1]
print(two_sum([3, 3], 6)) # Output: [0, 1]Common mistakes
- Using a list for "seen" checks: x in a_list is O(n), so the loop becomes O(n²). Fix: use a set or a dict.
- Storing x before looking for target − x, so [3] with target 6 pairs the 3 with itself. Fix: check first, then store.
- Using a list or a set as a key. Fix: convert it to a tuple (or a frozenset when order does not matter).
- Reading d[key] for a missing key, which raises KeyError. Fix: d.get(key, default), check key in d first, or use collections.defaultdict.
- Adding or deleting keys while looping over the same dict (RuntimeError: dictionary changed size during iteration). Fix: loop over list(d) or build a new dict.
- Saying "O(1) always". Fix: say "O(1) on average, O(n) in the worst case".
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Hash maps and hash sets · 1/8
This lesson is about hash maps and hash sets, the tools behind most fast interview solutions.
Prefer a video by a teacher? Find free videos on “hash table hash map explained collisions chaining open addressing two sum” on YouTube ↗
In real life: Stopping a double payment
When a payment request arrives twice (a slow network, a double tap on "Pay"), a payments service must not charge the customer twice. A common design gives every request a unique ID, often called an idempotency key, and remembers recently seen IDs in a hash set or a key-value store such as Redis. Before charging, it asks "have I seen this ID?", an O(1) average lookup even with millions of IDs. This is a simplified picture of how such systems typically work.
processed = set()
def charge(request_id, amount):
if request_id in processed:
return "duplicate ignored"
processed.add(request_id)
return f"charged Rs {amount}"
print(charge("TXN1001", 250))
print(charge("TXN1001", 250))
print(charge("TXN1002", 99))Practice (0/3 done)
Write two_sum(nums, target) that returns a list [i, j] of two DIFFERENT indexes with nums[i] + nums[j] == target and i < j. Exactly one such pair exists. Use a dict so it runs in O(n) time (O(n) extra space). Example: two_sum([2, 7, 11, 15], 9) returns [0, 1].
Write contains_nearby_duplicate(nums, k) that returns True if there are two DIFFERENT indexes i and j with nums[i] == nums[j] and abs(i - j) <= k, else False. O(n) time. Example: contains_nearby_duplicate([1, 2, 3, 1], 3) returns True (indexes 0 and 3).
Write intersection(a, b) that returns the values that appear in BOTH lists, each value once, sorted in increasing order. Use sets: about O(n + m) to find the common values (plus sorting the result). Example: intersection([4, 9, 5], [9, 4, 9, 8, 4]) returns [4, 9].
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Counting and grouping with hashingHashing, two pointers and sliding window · same module
- Two pointersHashing, two pointers and sliding window · same module
- Sliding window: fixed sizeHashing, two pointers and sliding window · same module
- Hash tablesC Programming (from zero to systems) · Data structures in C
- std::map and std::unordered_mapC++ Programming (modern C++ and DSA) · The Standard Template Library (STL)
- Big-O and searchingPython Programming · Algorithms, data structures and testing
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is the average cost of checking key in d for a Python dict with n keys?
Answer: O(1)
Hashing jumps almost straight to the right slot, so the average cost does not grow with n.
Which of these can be used as a key in a Python dict?
Answer: (1, 2)
Tuples of hashable items are hashable. Lists, sets and dicts can change, so they are unhashable.
In a hash table, what is a collision?
Answer: Two different keys that land in the same bucket
More possible keys than buckets means some keys must share a bucket; chaining or open addressing handles it.
In two-sum with a map, why look for target − x BEFORE storing x?
Answer: So an item is never paired with itself, for example nums = [3] with target 6
If 3 were stored first, the lookup for 6 − 3 = 3 would find the same element.
In JavaScript: const o = {}; o[1] = "a"; o["1"] = "b". How many keys does o have?
Answer: 1
Object keys are turned into strings, so 1 and "1" are the same key. A Map would keep them apart.
When can a hash map lookup become O(n)?
Answer: When many keys collide into the same bucket or probe sequence
If most keys share a bucket, a lookup has to check them one by one.
What is the average lookup time of a well-sized hash table?Related
What is the average cost of key in d for a Python dict d?Related
Answer: O(1)
Dicts use hashing, so membership is O(1) on average.
What is the worst-case lookup time of std::unordered_map?Related
Answer: O(n)
If many keys collide into one bucket, a lookup checks them one by one.
Which key puts exactly the anagrams of "listen" into one group AND can be used as a dict key?Related
Answer: "".join(sorted("listen"))
The sorted string is the same for all anagrams and is hashable. A set loses counts and is unhashable, a list is unhashable, and length groups unrelated words.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 4 more topics in “Hashing, two pointers and sliding window” before its exam: Counting and grouping with hashing, Two pointers, Sliding window: fixed size, Sliding window: variable size.
- Module 3 exam: Hashing, two pointers and sliding window · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Stacks, queues and linked lists. 61 topics left in the course before the final exam.