Hello! In the last module, you mastered trees, heaps, and priority queues, learning how to solve common "Top K" problems efficiently. Now, we're shifting gears to a data structure that is arguably the most important for coding interviews: the hash table.
This lesson introduces the fundamental concepts behind hash tables. Your goal is to be able to explain hashing, hash functions, and the two major collision resolution strategies: chaining and open addressing. A deep understanding of how hash tables work internally—achieving their famous average time complexity for lookups, insertions, and deletions—is a huge asset in technical interviews.
Let's begin by understanding why we need hashing in the first place.
1. The Quest for Constant-Time Search
We've seen that searching in an unsorted array takes time, while a sorted array allows for binary search. Can we do even better? The driving motivation behind hashing is to achieve an average search time of .
The following video provides an excellent introduction to this idea. It starts by comparing hashing to linear and binary search and then introduces the core concept of mapping a key directly to an array index.
Hashing Technique - Simplified
Watch this section from 'Hashing Technique - Simplified' by Abdul Bari to understand the motivation for hashing and the fundamental idea of using a function to map keys to array indices.
Watch from the beginning (00:00) to 07:10. Pay attention to how the 'ideal' hash function H(X) = X is impractical due to space, and how a more realistic function like H(X) = X mod size introduces the problem of collisions.
As the video explained, the core components of hashing are:
- Hash Table: An array that stores the data.
- Key: The data (or an identifier for the data) we want to store or retrieve.
- Hash Function: A function that takes a key and computes an index in the hash table. A simple example is
h(key) = key % table_size. - Collision: The scenario where two different keys map to the same index. Since collisions are practically unavoidable, handling them effectively is crucial.
2. What Makes a Good Hash Function?
A good hash function is the cornerstone of an efficient hash table. It should have two main properties:
- Fast Computation: It must be quick to calculate. If the hash function itself is slow, it defeats the purpose of aiming for operations. For strings, this is often where is the length of the string.
- Uniform Distribution: It should spread keys as evenly as possible across the available indices to minimize collisions.
Your experience with various programming languages and projects gives you a good footing to understand how different data types can be hashed.
Hashmap in Java | Internal Working of Hashmap in Java | Hashmap Implementation | DSA-One Course #30
This video from Anuj Kumar Sharma, in the context of Java's HashMap, discusses the properties of a good hash function and gives examples for different data types like integers, strings, and custom objects.
Watch from 02:46 to 08:39. Focus on how hash functions are designed to handle different key types and the importance of uniform distribution.
In Java, the hashCode() method provides the integer hash code for an object. This hash code is then typically mapped to a valid index in the hash table array, often using a modulo operation or bitwise tricks. For your custom classes, it's essential to correctly override both hashCode() and equals() to ensure they work correctly in hash-based collections like HashMap and HashSet.
3. Collision Resolution Strategies
Since even the best hash functions can produce collisions, we need robust strategies to handle them. There are two main families of techniques.

Let's explore each branch.
A) Separate Chaining (or Open Hashing)
This is the most straightforward and common strategy. The idea is to make each slot in the hash table a "bucket" that can hold multiple elements. This bucket is typically implemented as a linked list.
When a key is hashed to an index:
- Insertion: You add the new key-value pair to the linked list at that index. This is an operation if you add to the head of the list.
- Search/Deletion: You traverse the linked list at that index to find the key. The time complexity depends on the length of the list.

The performance of this method depends on the load factor, , defined as:
The average length of a chain is , so the average time for search and delete is . If we keep the load factor low (e.g., by resizing the table when gets too high), the performance remains effectively constant, .
This text from OpenDSA provides a formal explanation of Separate Chaining, discusses the load factor, and even touches on alternatives to linked lists.
Read the sections 'Separate Chaining, or Open Hashing' and 'Alternatives to a linked list'. This will solidify your understanding of the concept and its performance characteristics.
A key optimization in modern Java (HashMap since version 8) is that if a linked list at a bucket grows too long (e.g., beyond 8 elements), it is converted into a self-balancing binary search tree. This improves the worst-case lookup time for that bucket from to .
B) Open Addressing (or Closed Hashing)
In this strategy, all elements are stored directly within the hash table itself. If a collision occurs at index i, we "probe" or search for the next available slot according to a fixed sequence.
The three main probing methods are:
-
Linear Probing: If
h(key)is occupied, tryh(key) + 1, thenh(key) + 2, and so on, wrapping around the table if necessary.- Formula:
(h(key) + i) % mfori = 0, 1, 2, ... - Problem: This leads to primary clustering, where long runs of occupied slots build up, significantly degrading performance as searches have to traverse these clusters.
- Formula:
-
Quadratic Probing: To mitigate clustering, the probe sequence jumps quadratically. If
h(key)is occupied, tryh(key) + 1^2, thenh(key) + 2^2, and so on.- Formula:
(h(key) + i^2) % mfori = 0, 1, 2, ... - Problem: This avoids primary clustering but can suffer from secondary clustering, where keys that initially hash to the same location follow the exact same probe sequence.
- Formula:
-
Double Hashing: This method uses a second, independent hash function to determine the step size for the probe sequence, effectively eliminating both primary and secondary clustering.
- Formula:
(h1(key) + i * h2(key)) % mfori = 0, 1, 2, ...
- Formula:
The following video and article explain these techniques clearly.
Hashing Technique - Simplified
Let's return to Abdul Bari's video, which provides an excellent visual demonstration of how linear and quadratic probing work.
Watch from 07:10 to 17:05. This covers chaining, linear probing (and its clustering issue), and quadratic probing as a solution.
Open Addressing Collision Handling technique in Hashing
For a concise written summary and formulas for all three open addressing techniques, refer to this GeeksforGeeks article.
Read the introductory section and the descriptions for Linear Probing, Quadratic Probing, and Double Hashing.
Test your understanding!
Let's say you have a hash table of size 7 and a hash function h(key) = key % 7. Insert the following keys in order: 15, 22, 8, 1, 29, 14.
Show the final state of the hash table using:
- Separate Chaining (using linked lists)
- Open Addressing with Linear Probing
Show answer
First, let's find the hash index for each key:
15 % 7 = 122 % 7 = 1(Collision)8 % 7 = 1(Collision)1 % 7 = 1(Collision)29 % 7 = 1(Collision)14 % 7 = 0
1. Final State with Separate Chaining:
The keys that hash to the same index are added to a linked list at that index.
- Index 0:
-> 14 - Index 1:
-> 29 -> 1 -> 8 -> 22 -> 15(assuming insertion at the head of the list) - Index 2:
(empty) - Index 3:
(empty) - Index 4:
(empty) - Index 5:
(empty) - Index 6:
(empty)
2. Final State with Linear Probing:
We find the next available slot when a collision occurs.
15:15 % 7 = 1. Place15at index 1.22:22 % 7 = 1. Index 1 is full. Try index 2. Place22at index 2.8:8 % 7 = 1. Index 1 is full. Try 2 (full). Try 3. Place8at index 3.1:1 % 7 = 1. Index 1 is full. Try 2, 3 (full). Try 4. Place1at index 4.29:29 % 7 = 1. Index 1 is full. Try 2, 3, 4 (full). Try 5. Place29at index 5.14:14 % 7 = 0. Index 0 is empty. Place14at index 0.
The final array would look like: [14, 15, 22, 8, 1, 29, (empty)]
4. Comparison: Chaining vs. Open Addressing
Knowing the trade-offs between these two strategies is a classic interview topic.
Open Addressing Collision Handling technique in Hashing
This table from the same GeeksforGeeks article provides a direct comparison of the key differences between Separate Chaining and Open Addressing.
Study the comparison table. Pay special attention to points about cache performance, space usage, and implementation complexity (especially for deletion).
Here's a summary of the most important points:
- Space: Chaining uses extra space for pointers in the linked lists. Open addressing uses all space within the table itself.
- Cache Performance: Open addressing has better cache locality because all elements are in a contiguous array, which CPUs can cache effectively. Chaining can be slow as linked list nodes may be scattered in memory.
- Deletion: Deletion is simple in chaining (remove from list). In open addressing, it's complex; you can't just empty a slot, as it might break a probe chain for other elements. You must mark it with a special "deleted" value.
- Load Factor: Chaining works well even with . Open addressing requires and its performance degrades sharply as approaches 1.
In practice, Separate Chaining is used more often (it's the basis for Java's HashMap), largely due to its simpler implementation and more graceful performance degradation as the table fills up.
Conclusion
You've now covered the theoretical foundation of hashing. This is a critical first step, and these concepts are frequently tested in interviews.
Key Takeaways:
- Hashing aims to provide average time for search, insertion, and deletion by mapping keys directly to array indices.
- A good hash function is fast and distributes keys uniformly to minimize collisions.
- Collisions are inevitable and are handled by two main strategies:
- Separate Chaining: Each table slot is a bucket (like a linked list) for colliding keys. It's simple and handles high load factors well.
- Open Addressing: Colliding keys are placed in other empty slots within the table using a probe sequence (linear, quadratic, or double hashing). It has better cache performance but is more complex to implement and sensitive to load factor.
Preview of the Next Lesson:
Theory is great, but your goal is to excel in practical coding interviews. In the next lesson, you will put this knowledge into practice by implementing a hash table from scratch using the Separate Chaining method in Java. This will solidify your understanding and prepare you for a common hands-on interview task.