Search in a hash table that maintains elements within a small neighborhood of their home bucket.
Hopscotch hashing maintains the invariant that each key resides within a small fixed distance from its home bucket. This distance is called the neighborhood size. Lookup only scans this bounded region, so it remains fast and cache friendly even at high load.
You search by computing the home index and checking a compact neighborhood using a bitmap or mask that records which nearby slots are occupied by keys belonging to that bucket.
Problem
Given a hopscotch hash table and a key , return the associated value if it exists.
Model
- Table of size
- Hash function
- Neighborhood size
Each bucket owns positions:
A bitmap at marks which offsets in this range contain keys whose home is .
Algorithm
hopscotch_lookup(T, k):
i = h(k)
mask = T[i].bitmap
for offset from 0 to H - 1:
if mask has bit offset set:
j = (i + offset) mod m
if T[j].key == k:
return T[j].value
return NOT_FOUNDExample
Let:
Suppose bucket 3 has bitmap:
bitmap = 1010This means keys for bucket 3 appear at offsets 1 and 3:
| offset | index |
|---|---|
| 1 | 4 |
| 3 | 6 |
Search for a key with home bucket 3:
- compute
- check positions 4 and 6
- compare keys until match found or exhausted
Correctness
Insertion ensures that every key stays within its home bucket neighborhood and that the bitmap correctly records its position. Lookup reads this bitmap and checks exactly those positions.
If the key exists, it must appear in one of these marked offsets. If none match, the key does not exist.
Complexity
| case | time |
|---|---|
| worst | |
| average |
Since is a fixed constant, lookup runs in constant time.
Space
Each bucket stores a small bitmap in addition to the table entries.
When to Use
Hopscotch hashing is appropriate when:
- high load factors are required
- predictable constant time lookup is needed
- cache locality is important
- concurrent or lock free variants are desired
It offers better locality and stability than linear probing while keeping lookup bounded.
Implementation
class HopscotchHashTable:
def __init__(self, m=16, H=4):
self.m = m
self.H = H
self.table = [None] * m
self.bitmap = [0] * m
def _hash(self, key):
return hash(key) % self.m
def get(self, key):
i = self._hash(key)
mask = self.bitmap[i]
for offset in range(self.H):
if (mask >> offset) & 1:
j = (i + offset) % self.m
k, v = self.table[j]
if k == key:
return v
return Nonetype Entry[K comparable, V any] struct {
Key K
Value V
}
type HopscotchHashTable[K comparable, V any] struct {
Table []Entry[K, V]
Bitmap []uint32
M int
H int
}
func (h *HopscotchHashTable[K, V]) hash(key K) int {
return int(uintptr(any(key))) % h.M
}
func (h *HopscotchHashTable[K, V]) Get(key K) (V, bool) {
i := h.hash(key)
mask := h.Bitmap[i]
for offset := 0; offset < h.H; offset++ {
if (mask>>offset)&1 == 1 {
j := (i + offset) % h.M
if h.Table[j].Key == key {
return h.Table[j].Value, true
}
}
}
var zero V
return zero, false
}