DEV Community

Cover image for Hash Tables
Shankar L
Shankar L

Posted on

Hash Tables

Why should you care?

Imagine you have one million user records and you need to find a user's information using their username.

Searching one record at a time could take a long time.

A hash table can often find the required value in O(1) average time.

Hash tables power many things you use every day:

  • Dictionaries and maps
  • Database indexing
  • Caches
  • Symbol tables in compilers
  • Counting frequencies
  • Duplicate detection
  • Sets
  • Fast lookups in applications

Understanding hash tables also teaches an important computer science lesson:

You can trade memory for speed.


The Problem

Suppose we have these student records:

ID       Name
101      Arun
205      Priya
309      Ravi
412      Kumar
Enter fullscreen mode Exit fullscreen mode

If we store them in an array:

[101, Arun]
[205, Priya]
[309, Ravi]
[412, Kumar]
Enter fullscreen mode Exit fullscreen mode

and someone asks:

"Who is student 309?"
Enter fullscreen mode Exit fullscreen mode

We might have to check:

101 → no
205 → no
309 → found
Enter fullscreen mode Exit fullscreen mode

With a large collection, this can become O(n).

We want a way to take a key such as:

309
Enter fullscreen mode Exit fullscreen mode

and quickly determine where its value should be stored.

That's the problem hash tables solve.


The Concept

A hash table is a data structure that stores key-value pairs and uses a hash function to determine where a key should be placed.

For example:

Key → Value

101 → Arun
205 → Priya
309 → Ravi
412 → Kumar
Enter fullscreen mode Exit fullscreen mode

The basic process is:

Key
 ↓
Hash Function
 ↓
Hash Value
 ↓
Array Index
 ↓
Stored Value
Enter fullscreen mode Exit fullscreen mode

For example:

309
 ↓
hash(309)
 ↓
some number
 ↓
index 4
 ↓
"Ravi"
Enter fullscreen mode Exit fullscreen mode

The hash table therefore combines two ideas:

Hash Function + Array
Enter fullscreen mode Exit fullscreen mode

The array provides fast indexed access, while the hash function determines which index to use.


Simple Explanation

Imagine you have 10 numbered lockers:

0  1  2  3  4  5  6  7  8  9
Enter fullscreen mode Exit fullscreen mode

You need to store information about different students.

Instead of deciding manually which locker to use, you create a rule:

locker = studentID % 10
Enter fullscreen mode Exit fullscreen mode

For student 123:

123 % 10 = 3
Enter fullscreen mode Exit fullscreen mode

So:

Student 123
     ↓
123 % 10
     ↓
     3
     ↓
Locker 3
Enter fullscreen mode Exit fullscreen mode

Another student:

456 % 10 = 6
Enter fullscreen mode Exit fullscreen mode

So student 456 goes into locker 6.

The hash function essentially acts as a rule for converting a key into an array position.

Real hash functions are more sophisticated than this example, but the basic idea is the same.


Real-world Analogy

Imagine a huge library.

Instead of searching every book one by one, each book receives a classification number.

For example:

Book → Classification → Shelf
Enter fullscreen mode Exit fullscreen mode

You don't walk through the entire library looking for a book.

You calculate or look up its classification and go directly to the appropriate area.

A hash table works similarly:

Key
 ↓
Hash
 ↓
Location
 ↓
Value
Enter fullscreen mode Exit fullscreen mode

The goal is to avoid searching through every stored item.


Code Example

In Java, HashMap provides a hash-table-based map.

import java.util.*;

public class Main {
    public static void main(String[] args) {

        HashMap<Integer, String> students = new HashMap<>();

        students.put(101, "Arun");
        students.put(205, "Priya");
        students.put(309, "Ravi");

        System.out.println(students.get(309));
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Ravi
Enter fullscreen mode Exit fullscreen mode

We inserted:

students.put(309, "Ravi");
Enter fullscreen mode Exit fullscreen mode

and later retrieved it using:

students.get(309);
Enter fullscreen mode Exit fullscreen mode

The important idea is that we don't need to search every student manually.

We give the map the key:

309
Enter fullscreen mode Exit fullscreen mode

and it uses hashing to determine where the corresponding value can be found.

Checking whether a key exists

if (students.containsKey(309)) {
    System.out.println("Student exists");
}
Enter fullscreen mode Exit fullscreen mode

Removing a key

students.remove(309);
Enter fullscreen mode Exit fullscreen mode

Updating a value

students.put(101, "Arun Kumar");
Enter fullscreen mode Exit fullscreen mode

If key 101 already exists, its value is replaced.


Common Mistakes

Mistake 1: Assuming hash tables are always O(1)

You will often hear:

"Hash table lookup is O(1)."

That's an oversimplification.

The more accurate statement is:

Hash table lookup is O(1) on average, assuming a good hash function and controlled collisions.

In unfavorable situations, operations can degrade toward O(n) depending on the implementation and collision behavior.

So:

Average: O(1)
Worst case: potentially O(n)
Enter fullscreen mode Exit fullscreen mode

Mistake 2: Thinking different keys always produce different indexes

This is false.

Consider:

hash(101) % 10 = 1
hash(111) % 10 = 1
Enter fullscreen mode Exit fullscreen mode

Both keys produce the same index.

This is called a collision.

101 ──┐
      ├──→ Index 1
111 ──┘
Enter fullscreen mode Exit fullscreen mode

A hash table therefore needs a mechanism for handling collisions.


Mistake 3: Confusing a hash value with an array index

A hash function might produce a very large integer:

hash(key) = 284739284
Enter fullscreen mode Exit fullscreen mode

But your table might contain only 100 buckets.

You need to map the hash to a valid index, conceptually:

index = hash(key) % tableSize
Enter fullscreen mode Exit fullscreen mode

So:

Hash value ≠ necessarily array index
Enter fullscreen mode Exit fullscreen mode

The hash is used to derive the index.


Advanced Notes

1. Hash functions

A hash function converts a key into a numerical hash value.

Conceptually:

h(key) → integer
Enter fullscreen mode Exit fullscreen mode

A good hash function should distribute keys reasonably evenly across the table.

For example:

Keys
 ↓
Hash Function
 ↓
┌────┬────┬────┬────┬────┐
│    │ A  │    │ B  │    │
└────┴────┴────┴────┴────┘
Enter fullscreen mode Exit fullscreen mode

Poor distribution can cause many collisions.

Good distribution helps maintain fast operations.


2. Collisions

A collision occurs when different keys map to the same bucket.

For example:

Key A ──┐
        ├──→ Bucket 3
Key B ──┘
Enter fullscreen mode Exit fullscreen mode

There are several ways to handle collisions.

The two major approaches are:

  1. Separate chaining
  2. Open addressing

3. Separate chaining

With separate chaining, each bucket can contain multiple entries.

Conceptually:

Bucket 0 → empty
Bucket 1 → [101, Arun] → [111, Priya]
Bucket 2 → [202, Ravi]
Bucket 3 → empty
Enter fullscreen mode Exit fullscreen mode

The bucket essentially contains a small collection of entries.

When looking for key 111:

111
 ↓
hash
 ↓
Bucket 1
 ↓
Search entries in bucket
 ↓
111 → Priya
Enter fullscreen mode Exit fullscreen mode

This is one common way hash tables handle collisions.


4. Open addressing

With open addressing, all entries are stored directly inside the table.

If the calculated position is occupied, the table searches for another available position according to a probing strategy.

For example:

Index:  0    1    2    3    4
       ┌────┬────┬────┬────┬────┐
       │    │101 │    │111 │    │
       └────┴────┴────┴────┴────┘
Enter fullscreen mode Exit fullscreen mode

One common strategy is linear probing:

index
  ↓
3 occupied
  ↓
try 4
  ↓
if occupied, try 5
  ↓
...
Enter fullscreen mode Exit fullscreen mode

Other strategies include quadratic probing and double hashing.


5. Load factor

One important concept is the load factor.

It is approximately:

Load Factor = Number of Stored Entries / Number of Buckets
Enter fullscreen mode Exit fullscreen mode

For example:

Entries = 7
Buckets = 10

Load Factor = 7 / 10 = 0.7
Enter fullscreen mode Exit fullscreen mode

As the table becomes more crowded, collisions generally become more likely.

When the load factor becomes too high, implementations may resize and rehash the table.


6. Rehashing

Suppose a table has 5 buckets:

0  1  2  3  4
Enter fullscreen mode Exit fullscreen mode

and becomes too full.

The implementation may create a larger table:

0  1  2  3  4  5  6  7  8  9
Enter fullscreen mode Exit fullscreen mode

The entries may need to be hashed again because their indexes depend on the table size.

This process is called rehashing.

Although resizing can be expensive at that moment, it helps maintain efficient average performance over many operations.


7. HashSet

A hash table doesn't always need to store a separate value.

Sometimes you only care whether something exists.

For example:

HashSet<Integer> numbers = new HashSet<>();

numbers.add(10);
numbers.add(20);
numbers.add(30);

System.out.println(numbers.contains(20));
Enter fullscreen mode Exit fullscreen mode

Output:

true
Enter fullscreen mode Exit fullscreen mode

A HashSet is useful when the primary requirement is membership testing and uniqueness.

Conceptually:

HashMap:
Key → Value

HashSet:
Key → Exists
Enter fullscreen mode Exit fullscreen mode

8. Hashing strings

Hash tables aren't limited to integers.

You can use strings as keys:

HashMap<String, Integer> ages = new HashMap<>();

ages.put("Arun", 21);
ages.put("Priya", 22);

System.out.println(ages.get("Priya"));
Enter fullscreen mode Exit fullscreen mode

Output:

22
Enter fullscreen mode Exit fullscreen mode

The string is passed through a hash function to produce a hash value.

So the same fundamental process applies:

"Priya"
   ↓
hashCode()
   ↓
Hash value
   ↓
Bucket
   ↓
22
Enter fullscreen mode Exit fullscreen mode

9. Keys and equality

A subtle but important point is that hashing alone isn't enough.

Two different keys can have the same hash:

hash(A) = 42
hash(B) = 42
Enter fullscreen mode Exit fullscreen mode

That doesn't mean:

A == B
Enter fullscreen mode Exit fullscreen mode

Hash tables generally use both:

Hashing
+
Equality comparison
Enter fullscreen mode Exit fullscreen mode

to correctly identify the requested key.

This is particularly important when creating custom key objects in languages such as Java.


The Bigger Picture

Hash tables connect several concepts you've already learned:

Arrays
   ↓
Fast indexed access
   ↓
Hash Function
   ↓
Hash Table
   ↓
Fast Key-Based Lookup
Enter fullscreen mode Exit fullscreen mode

They also connect to other major data structures:

Arrays
  ├── Hash Tables
  ├── Dynamic Arrays
  └── Heaps

Linked Lists
  └── Collision Handling

Hash Tables
  ├── HashMap
  ├── HashSet
  └── Caches
Enter fullscreen mode Exit fullscreen mode

And they appear constantly in algorithms.

For example, suppose you need to determine whether an array contains duplicates:

[4, 7, 2, 4, 9]
Enter fullscreen mode Exit fullscreen mode

A naive approach could compare every pair.

A hash set gives a much faster average approach:

4 → add
7 → add
2 → add
4 → already exists → duplicate
Enter fullscreen mode Exit fullscreen mode

This changes the typical time complexity from O(n²) to O(n) average time, at the cost of additional memory.

That's the memory-for-speed trade-off in action.


The Most Important Mental Model

A hash table is an array that uses a hash function to decide where a key should look.

Remember this pipeline:

       Key
        ↓
  Hash Function
        ↓
   Hash Value
        ↓
   Bucket / Index
        ↓
      Value
Enter fullscreen mode Exit fullscreen mode

For example:

"Shankar"
    ↓
  hash()
    ↓
  738291
    ↓
  index 7
    ↓
  stored value
Enter fullscreen mode Exit fullscreen mode

And remember one critical exception:

Different keys
      ↓
Can produce
      ↓
Same bucket
      ↓
Collision
Enter fullscreen mode Exit fullscreen mode

A good hash table isn't one that never has collisions.

It's one that handles collisions efficiently.


Summary

A hash table stores data as key-value pairs and uses hashing to provide fast average-case lookup.

The key ideas are:

  • A hash function converts a key into a hash value.
  • The hash value helps determine the storage bucket/index.
  • Hash tables provide O(1) average-case insertion, lookup, and deletion.
  • Different keys can produce the same bucket, causing collisions.
  • Collisions can be handled using techniques such as chaining and open addressing.
  • Load factor measures how full the table is.
  • Tables may resize and rehash when they become too crowded.
  • HashMap is used for key-value mappings.
  • HashSet is useful for uniqueness and membership testing.
  • Hash tables trade additional memory for fast access.

A hash table turns the question "Where is this value?" into a calculation, transforming potentially expensive searches into near-constant-time lookups—and that idea is one of the most powerful tools in algorithm design.

Top comments (0)