Arrays & Hashing
- Explain how dynamic arrays resize and why append is amortized O(1)
- Describe the hash function โ bucket โ collision chain that makes dict/set O(1) average
- State why dict keys must be hashable and what load factor forces a resize
- Apply the three hashing patterns: frequency counting, dedupe/seen-sets, and canonical keys
- Solve Two Sum, Valid Anagram, and Group Anagrams in one pass each
| Spaced-rep warm-up: Day 22 ladder + Week 3 due cards | 10 min |
| ELI5 + tech read, watch the hash-buckets visualizer | 20 min |
| Guided: build the toy hash map + three classics | 40 min |
| Practice: two problems solo | 20 min |
| Project: start patterns.md (Hashing section) | 20 min |
| Quiz + flashcards | 10 min |
Builds on: Day 4 โ Collections โ lists and dicts ยท Day 22 โ Big O & complexity
A warehouse with numbered shelves is an array: tell the clerk "shelf 4,217" and she walks straight there โ one step, no searching โ because shelf positions are computed, not hunted for. But ask her "which shelf holds the red vase?" and she must walk every aisle: numbers give instant access by POSITION, and nothing by CONTENT.
Now the coat check at a theater. You hand over your coat; the attendant doesn't remember where every coat is โ she runs a little rule on your ticket number ("ticket 731 โ hook 31") and walks straight to the hook. That rule is a hash function: it converts the thing you're looking up into a shelf number, so content-based lookup becomes position-based lookup โ straight to the page instead of reading the whole book. Occasionally two tickets map to the same hook (a collision); she hangs both coats there and checks the tags โ still fast, as long as no hook gets overloaded. When the rack gets crowded, the theater installs a bigger rack and re-hangs everything (a resize). Python's dict and set are exactly this coat check, and they are the single most useful object in your interview toolkit.
Hashing is the workhorse of practical speed: the Day 22 fix ("replace the list scan with a set") IS this lesson, and it recurs everywhere โ deduplicating documents in a RAG ingest pipeline (Day 113), caching LLM responses by prompt hash (Day 156), counting token frequencies (Day 96). In interviews, "can I trade O(n) memory for O(1) lookup?" is the first question to ask of almost every array problem โ roughly a third of easy/medium problems fall to a hash map. Knowing WHY it's O(1) average (and when it isn't) is what separates users from engineers.
Straight to the page โ how a dict finds keys in O(1)
step 1 / 7Five empty buckets. A hash function turns any key into a number; key % 5 picks the bucket. No searching โ the key COMPUTES its own address.
Guided practice
Build the coat check โ a toy hash map
25 min- Create
hashmap_lab.pyand implement theToyHashMapfrom the starter skeleton: 8 buckets, each a list of (key, value) pairs (chaining). - Fill in
_bucket_index,put, andget. Run the test block at the bottom โ all four asserts should pass. - Add a
stats()method printing each bucket's length, then insert 20 string keys and look at the distribution โ roughly even is the hash function doing its job. - Sabotage: replace
hash(key)withlen(key)in_bucket_indexand re-run stats with 20 words of similar length. Watch one bucket swell โ you have manufactured the O(n) worst case and now understand it forever. Restorehash. - In a comment, answer: why must the table also store the KEY in the bucket, not just the value?
The three patterns in anger
15 minSolve the three classics in hashing_classics.py, each with a one-line pattern note before the function:
- Two Sum (seen-set/complement): given nums and target, return indices of two numbers summing to target โ one pass, store number โ index, check for the complement BEFORE inserting.
- Valid Anagram (frequency map): are two strings anagrams? Compare Counters โ or two 26-slot tuples for the purist version.
- Group Anagrams (canonical key): group a list of words so all anagrams share a list. Key:
tuple(sorted(word)).
For each: state time and space complexity in a comment, and test with the provided cases plus one edge case you invent (empty string, duplicate numbersโฆ).
On your own
Two on your own
20 minNo scaffolding. Solve both, stating complexity before you code:
(1) First unique character: given a string, return the index of the first character that appears exactly once, or -1. Target O(n) โ two passes allowed.
(2) Longest consecutive sequence: given an unsorted list of ints, return the length of the longest run of consecutive values (e.g. [100, 4, 200, 1, 3, 2] โ 4 for 1-2-3-4). Target O(n) โ sorting is the O(n log n) trap.
Hints (only if stuck): for (2), put everything in a set; only start counting from numbers whose predecessor is NOT in the set โ that guard is what keeps it linear.
Start the pattern log
Create patterns.md in your practice repo โ the living document that carries you to Day 179. Add today's first section, "Hashing," with: (1) a three-line summary of when to reach for a hash map (need content-based lookup / counting / grouping in O(1) per item); (2) your solved classics (Two Sum, Valid Anagram, Group Anagrams, plus today's practice pair) each with a one-line "the trick wasโฆ" note; (3) the recognition cues โ phrases in a problem statement that smell like hashing ("appears once", "find pairs", "group by", "seen before"); (4) the honest costs: O(n) extra space, O(1) AVERAGE not worst, keys must be immutable. Commit it. Every pattern day this phase adds a section.
Common mistakes & misconceptions
- Using a list as a dict key and being surprised by TypeError: unhashable. Mutable objects can't promise a stable hash; convert to tuple (or frozenset) first.
- Calling dict lookup O(1) unconditionally. It is O(1) average; collisions make it O(n) worst-case โ the sabotage step showed you exactly how.
- Solving Two Sum by inserting first, then checking โ which matches a number with itself. Check for the complement before inserting the current number.
- Reaching for sorting when a frequency map answers in O(n). "Compare compositions" (anagrams) needs counts, not order.
- Forgetting that insert(0, x) and pop(0) on a list shift every element โ O(n). If you need cheap operations at both ends, that is tomorrow's deque.
- Choosing a canonical key that isn't canonical: "".join(set(word)) loses counts and order โ "aab" and "ab" collide. sorted-tuple or count-tuple, nothing cleverer.
Q1. Why is looking up a key in a dict O(1) on average, regardless of size?
Q2. my_dict[[1, 2]] = "x" raises TypeError. Why?
Q3. Best canonical key to group anagrams under one dict entry?
Go deeper โ curated resources
- Python dict internals: compact dicts & insertion order โ Since 3.7, dicts preserve insertion order via a compact two-array layout. Skim how the entries array + sparse index table work โ it explains both the ordering guarantee and the memory win.
- ToyHashMap works, sabotage experiment observed and explained
- Five classics solved with stated complexities; longest-consecutive is O(n)
- patterns.md committed with the Hashing section
- Quiz โฅ 2/3
โ Back: Day 4 taught you to USE dict and set; today you know why they are fast and when they are not. Day 22's "swap the list scan for a set" now has a mechanism behind it.
Forward โ: Tomorrow's sliding window pairs with a hash map for its hardest problems. The hash-as-address idea returns as cache keys on Day 156 and โ transformed into geometry โ as the "meaning-based index" of vector databases on Day 115.
Unlocks: D24 Two Pointers & Sliding Window ยท D25 Stacks & Queues ยท D28 Week 4 Checkpoint: Pattern Drill ยท D30 Heaps, Priority Queues & Tries