Skip to content

Separate Chaining Search

Resolve hash collisions by storing multiple elements in buckets using linked structures.

Separate chaining handles collisions in a hash table by storing all elements that hash to the same index in a bucket. Each bucket is typically a linked list, though dynamic arrays or trees are also used.

You search by hashing the key and scanning only the corresponding bucket.

Problem

Given a hash table with chaining and a key kk, find the associated value if it exists.

Model

  • Table TT has size mm
  • Hash function h(k)h(k) maps keys to indices
  • Each T[i]T[i] stores a list of entries

Compute:

i=h(k) i = h(k)

Then search inside bucket T[i]T[i].

Algorithm

chaining_search(T, k):
    i = h(k)
    for each (key, value) in T[i]:
        if key == k:
            return value
    return NOT_FOUND

Example

Let m=5m = 5 and:

h(k)=kmod5 h(k) = k \bmod 5

Insert:

[12,7,22] [12, 7, 22]

Buckets:

indexbucket
2(12 → 7 → 22)

Search for k=22k = 22:

  • compute h(22)=2h(22) = 2
  • scan bucket: 12, 7, 22
  • match found

Correctness

All keys that hash to index ii are stored in bucket T[i]T[i]. The algorithm examines every element in that bucket. If kk exists, it must appear there, so it will be found.

Complexity

Let α=n/m\alpha = n/m be the load factor.

casetime
averageO(1+α)O(1 + \alpha)
worstO(n)O(n)

When α\alpha remains bounded, lookup runs in constant expected time.

Space

O(n+m) O(n + m)

Memory includes the table and all stored elements.

When to Use

Separate chaining is appropriate when:

  • table resizing is expensive or infrequent
  • deletion operations must be simple
  • load factor may grow beyond 1
  • memory overhead from pointers is acceptable

It handles high load factors better than open addressing.

Implementation

class HashTable:
    def __init__(self, m=8):
        self.m = m
        self.table = [[] for _ in range(m)]

    def _hash(self, key):
        return hash(key) % self.m

    def get(self, key):
        i = self._hash(key)
        for k, v in self.table[i]:
            if k == key:
                return v
        return None
type Entry[K comparable, V any] struct {
	Key   K
	Value V
}

type HashTable[K comparable, V any] struct {
	Table [][]Entry[K, V]
	M     int
}

func (h *HashTable[K, V]) hash(key K) int {
	return int(uintptr(any(key))) % h.M
}

func (h *HashTable[K, V]) Get(key K) (V, bool) {
	i := h.hash(key)

	for _, e := range h.Table[i] {
		if e.Key == key {
			return e.Value, true
		}
	}

	var zero V
	return zero, false
}