100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace

What is Hashing with Chaining?

Learn how hashing with chaining resolves collisions in a hash table, its time complexity, and how to answer this interview question.

mediumQ78 of 227 in Data Structures & Algorithms Est. time: 5 minsLast updated:
Open Code Lab

Expected Interview Answer

Hashing with chaining resolves hash collisions by storing every key that maps to the same bucket in a linked list (or dynamic array) attached to that bucket, so multiple keys can coexist at one index without overwriting each other.

When two keys hash to the same index, the table does not try to find them a different slot; instead it appends both to the bucket's list. A lookup hashes the key to find the bucket, then scans that short list comparing keys until it finds a match or reaches the end. With a good hash function and a load factor kept low by resizing, each bucket holds only a small constant number of entries on average, giving O(1) expected time for insert, search, and delete. The worst case degrades to O(n) only if every key collides into one bucket, which is why hash function quality and table resizing matter so much.

  • Simple to implement and reason about
  • Handles unlimited collisions per bucket gracefully
  • Deletion is straightforward, unlike open addressing
  • Performance degrades gracefully as load factor grows

AI Mentor Explanation

Chaining is like a stadium where every seat number maps to a designated meeting pole, and fans holding that same seat number due to a printing error simply form a line at that pole instead of fighting over one spot. A steward checks the pole for someone matching seat number and ticket name, walking the short line until a match is found. As more fans with duplicate numbers show up, the line at that pole grows, but other poles stay uncrowded if the numbering system is fair. This is exactly why chained hashing degrades gracefully: a busy bucket just means a longer line to walk, not a broken system.

Step-by-Step Explanation

  1. Step 1

    Hash the key

    Apply the hash function to the key and reduce it modulo the table size to get a bucket index.

  2. Step 2

    Append to the bucket list

    On insert, add the key-value pair to the linked list (or dynamic array) at that bucket.

  3. Step 3

    Search by scanning

    On lookup, hash to the bucket, then linearly compare keys in that bucket's list until a match or the end.

  4. Step 4

    Resize to control load factor

    When the average bucket size (load factor) exceeds a threshold, grow the table and rehash all entries.

What Interviewer Expects

  • Explain collisions are resolved by storing multiple entries per bucket
  • State expected O(1) time with a good hash function and low load factor
  • Name the worst case O(n) when all keys collide
  • Mention resizing/rehashing to keep load factor bounded

Common Mistakes

  • Claiming chaining guarantees O(1) worst case (it does not)
  • Forgetting to compare full keys within a bucket, not just the hash
  • Confusing chaining with open addressing / probing
  • Ignoring load factor and never resizing the table

Best Answer (HR Friendly)

โ€œHashing with chaining is how a hash table deals with two keys landing in the same slot: instead of fighting over the spot, it keeps a small list of everyone who landed there. I like it because it is simple, handles any number of collisions gracefully, and deletion is easy compared to other collision strategies.โ€

Code Example

Hash table with chaining
class ChainedHashTable:
    def __init__(self, size=8):
        self.size = size
        self.buckets = [[] for _ in range(size)]
        self.count = 0

    def _index(self, key):
        return hash(key) % self.size

    def put(self, key, value):
        bucket = self.buckets[self._index(key)]
        for i, (k, _) in enumerate(bucket):
            if k == key:
                bucket[i] = (key, value)
                return
        bucket.append((key, value))
        self.count += 1
        if self.count / self.size > 0.75:
            self._resize()

    def get(self, key):
        bucket = self.buckets[self._index(key)]
        for k, v in bucket:
            if k == key:
                return v
        raise KeyError(key)

    def _resize(self):
        old_buckets = self.buckets
        self.size *= 2
        self.buckets = [[] for _ in range(self.size)]
        self.count = 0
        for bucket in old_buckets:
            for k, v in bucket:
                self.put(k, v)

Follow-up Questions

  • How would you choose the initial size and growth factor for the table?
  • What makes a hash function "good" for minimizing collisions?
  • How does chaining compare to open addressing in memory usage?
  • How would you make this hash table thread-safe?

MCQ Practice

1. In hashing with chaining, what happens when two keys hash to the same bucket?

Chaining stores all colliding keys together in a list (or similar structure) attached to that bucket.

2. What is the expected time complexity of lookup in a well-tuned chained hash table?

With a good hash function and bounded load factor, each bucket holds a small constant number of entries, giving expected O(1) lookup.

3. What triggers a resize in a chained hash table?

Resizing keeps the average bucket length small by growing the table once the load factor crosses a set threshold.

Flash Cards

What data structure typically backs each bucket in chaining? โ€” A linked list or dynamic array of key-value pairs.

What is the worst-case time complexity of a lookup with chaining? โ€” O(n), if every key collides into a single bucket.

What keeps chaining performance close to O(1) in practice? โ€” A good hash function combined with resizing to keep the load factor low.

How is deletion handled in chaining vs open addressing? โ€” Chaining deletes by simply removing the entry from the bucket's list; open addressing requires tombstones or careful shifting.

1 / 4

Continue Learning