Hello! Welcome to the first lesson in our new module, "Hashing for Efficient Lookups."
In our last lesson, we saw a powerful application of combining prefix sums with another data structure to solve the "Subarray Sum Equals K" problem. The key was the ability to instantly check if we had seen a specific prefix sum before. That "instant check" capability is the magic of hash tables, and today, we're going to uncover exactly how that magic works.
Your learning outcome for this lesson is to explain hash table fundamentals, including hash functions and collision resolution strategies. Understanding these internals is crucial. In interviews, you'll constantly use hash maps (like Python's dict or Java's HashMap), and being able to analyze their performance and limitations will set you apart.
1. The Need for Speed: Beyond Arrays
Imagine you're building a system to manage user profiles. You want to retrieve a user's data using their unique username.
If you store the profiles in an array or list, finding a user by their username would require a linear search—iterating through the array one by one. This is an operation, which becomes prohibitively slow as the number of users grows. We need a way to get directly to the data, much like using an index to access an element in an array in time.
This is the problem hash tables were designed to solve. They provide a mechanism to map a key (like a username) to a value (the user profile) with an average time complexity of for lookups, insertions, and deletions.
HashMaps & Dictionaries, Explained Simply
To start, let's watch a short clip from the video "HashMaps & Dictionaries, Explained Simply" by Nic Barker. It perfectly frames the problem and introduces the hash map as the solution.
Watch the first two minutes of the video (00:00 - 01:59). Focus on why a simple array is inefficient for key-based lookups and how a hash map bridges this gap.
2. The Core Components: Hash Functions and Buckets
So, how does a hash table convert a key (like the string "johndoe") into an array index? This is done through a two-step process involving a hash function and the modulo operator.
The Hash Function
A hash function is a special algorithm that takes an input key and transforms it into an integer, known as a hash code.
A good hash function should have these properties:
- Deterministic: The same key must always produce the same hash code.
- Uniform Distribution: It should spread keys out as evenly as possible across the potential hash codes to avoid bunching them together.
- Fast: The computation itself should be very quick.
For example, a simple (though not very good) hash function for a string could be to sum the ASCII values of its characters.
From Hash Code to Array Index
The hash code produced can be a very large number. To map it to an index within our underlying array of a fixed size m, we use the modulo operator (%).
This calculation guarantees that the resulting index will be within the bounds of our array, from 0 to m-1. Each slot in this array is often called a bucket.
Hash Tables and Hash Functions
The video "Hash Tables and Hash Functions" provides a great visual demonstration of this process. It shows how a string key is converted into an array index using the ASCII sum and modulo method.
Watch the segment from 01:07 to 04:50. Pay attention to how names are converted to numbers (hash codes) and then mapped to indices in the array (buckets).
While the simple "remainder method" is easy to understand, more sophisticated hash functions exist, like the folding method (breaking the key into chunks and adding them) or the mid-square method (squaring the key and extracting the middle digits). You can explore these further in the "Hashing" article from Runestone Academy (resource ID LINK), under the section "Hash Functions."
3. The Inevitable Problem: Collisions
What happens if two different keys produce the same index? For example, using a table of size 11 and the remainder method, both keys 23 and 34 would map to index 1 (23 % 11 = 1 and 34 % 11 = 1). This is called a collision.
Since the number of possible keys is usually far greater than the number of available buckets, collisions are a fact of life. A robust hash table implementation must have a strategy to handle them. These strategies are broadly divided into two categories: Separate Chaining and Open Addressing.

4. Collision Resolution Strategy 1: Separate Chaining
This is the most common collision resolution strategy. The idea is simple: instead of each bucket holding just one value, it holds a pointer to another data structure—typically a linked list—that stores all the key-value pairs that hashed to that index.
- Insertion: When a collision occurs, you simply append the new key-value pair to the linked list at that bucket.
- Lookup: You hash the key to find the correct bucket, then perform a quick search through the small linked list to find the key you're looking for.

Hash Tables and Hash Functions
Let's watch a brief explanation of chaining.
Watch the clip from 09:08 to 10:35. It shows how the array from the previous example is populated using linked lists to handle collisions.
The Tradeoff: Chaining is simple to implement and handles a high density of elements gracefully. However, in the worst-case scenario where a poor hash function maps all keys to the same bucket, the hash table degrades into a single linked list, and all operations become .
5. Collision Resolution Strategy 2: Open Addressing
In open addressing, all key-value pairs are stored directly within the main array. When a collision occurs, the algorithm probes for the next available bucket according to a fixed sequence.
There are three common probing techniques:
-
Linear Probing: If a collision occurs at
index, checkindex + 1, thenindex + 2, and so on, until an empty bucket is found.- Problem: This can lead to primary clustering, where long runs of occupied buckets form, degrading performance as insertions and lookups have to traverse these clusters.
-
Quadratic Probing: Instead of stepping linearly, you check
index + 1^2,index + 2^2,index + 3^2, etc. This helps break up the large clusters formed by linear probing. -
Double Hashing: A second hash function is used to determine the step size for probing. This is the most effective at avoiding clustering but is also the most complex to implement.
HashMaps & Dictionaries, Explained Simply
The "HashMaps & Dictionaries, Explained Simply" video gives an excellent and intuitive walkthrough of open addressing with linear probing, including its challenges.
Watch from 12:05 to 18:54. This segment clearly explains how linear probing works, the problem of clustering, and the difficulty of handling deletions (which often requires using special 'tombstone' markers instead of actually removing the item).
6. Managing Performance: Load Factor and Rehashing
The performance of a hash table is highly dependent on how full it is. We measure this with the load factor, .
As increases, the probability of collisions rises, and performance suffers. To combat this, hash tables automatically resize themselves. When the load factor exceeds a certain threshold (typically around 0.7 for open addressing), a process called rehashing occurs:
- A new, larger array is created (often double the size).
- All existing key-value pairs are iterated over, and their hashes are re-calculated for the new array size.
- Each element is inserted into its new position in the larger array.
Rehashing is an operation, which can cause a temporary performance spike. However, since it happens infrequently, the cost is amortized over many insertions, and the average (amortized) time complexity for insertion remains .
Test your understanding!
You have a hash table of size 7. You need to insert the keys: 15, 22, 8, 1. Your hash function is key % 7.
- Where does each key get placed if you use Separate Chaining?
- Where does each key get placed if you use Open Addressing with Linear Probing?
Show answer
The hash values are:
15 % 7 = 122 % 7 = 1(Collision!)8 % 7 = 1(Collision!)1 % 7 = 1(Collision!)
-
Separate Chaining: All four keys hash to index 1. The bucket at index 1 would point to a linked list containing
15 -> 22 -> 8 -> 1(or some other order, depending on insertion logic). All other buckets (0, 2, 3, 4, 5, 6) would be empty. -
Open Addressing with Linear Probing:
- Insert 15:
15 % 7 = 1. Place 15 at index 1. - Insert 22:
22 % 7 = 1. Index 1 is taken. Probe to index 2. Place 22 at index 2. - Insert 8:
8 % 7 = 1. Index 1 is taken. Probe to index 2 (taken). Probe to index 3. Place 8 at index 3. - Insert 1:
1 % 7 = 1. Index 1 is taken. Probe to 2 (taken), 3 (taken). Probe to index 4. Place 1 at index 4.
The final table would look like:[empty, 15, 22, 8, 1, empty, empty]
- Insert 15:
To consolidate your understanding of these collision strategies, the following resource provides an excellent summary.
The article "Hash Map Deep Dive" by Gergely Orosz has a fantastic table that summarizes and compares the different collision resolution methods across various metrics.
Navigate to the section titled 'Collision Resolution'. Read through the descriptions of Chaining and the different Open Addressing methods, then focus on the summary table at the end of the section. This table is an invaluable reference for interviews.
Conclusion
You've just completed a deep dive into the engine room of one of computing's most important data structures. While you'll rarely implement a hash table from scratch in an interview, knowing what's happening under the hood is non-negotiable for a strong candidate.
Key Takeaways:
- Purpose: Hash tables provide average time for inserts, deletes, and lookups by mapping keys to array indices.
- Mechanism: A hash function computes a hash code from a key, and the modulo operator maps that code to a bucket in an array.
- Collisions: They are inevitable. The two main resolution strategies are Separate Chaining (linked lists in each bucket) and Open Addressing (probing for the next free bucket).
- Performance: The load factor measures how full the table is. Rehashing to a larger array is done to keep the load factor low and maintain amortized performance. The worst-case performance for all operations is .
In our next lesson, we will move from theory to practice. You'll learn how to apply hash maps and hash sets to solve a very common category of interview problems: frequency counting.