Cuckoo Hashing Lookup

Look up a key in a cuckoo hash table by checking a small fixed set of candidate positions.

Cuckoo Hashing Lookup

Cuckoo hashing stores each key in one of several possible table positions. In the common two hash version, a key can live at either $h_1(k)$ or $h_2(k)$. Lookup is therefore simple: compute both positions and check them.

The search operation is fast because it does not scan a bucket or probe a long sequence. The cost is moved mostly to insertion, where an existing key may be displaced and moved to its alternate position.

Problem

Given a cuckoo hash table and a key $k$, return the associated value if it exists.

Model

A two hash cuckoo table has:

  • table $T$
  • size $m$
  • hash functions $h_1$ and $h_2$

A stored key $k$ must satisfy:

$$ k \text{ is stored at } T[h_1(k)] \text{ or } T[h_2(k)] $$

Algorithm

cuckoo_lookup(T, k):
    i = h1(k)
    if T[i] is occupied and T[i].key == k:
        return T[i].value

    j = h2(k)
    if T[j] is occupied and T[j].key == k:
        return T[j].value

    return NOT_FOUND

Example

Let:

  • $m = 11$
  • $h_1(k) = k \bmod 11$
  • $h_2(k) = 1 + (k \bmod 10)$

For key $24$:

$$ h_1(24) = 2 $$

$$ h_2(24) = 5 $$

Lookup checks only positions 2 and 5.

step index result
1 2 no match
2 5 match

So the value stored with key $24$ is returned.

Correctness

The cuckoo hashing invariant says that every stored key appears in one of its candidate positions. For the two hash version, these positions are exactly $h_1(k)$ and $h_2(k)$.

The lookup checks both candidate positions. If either contains $k$, it returns the associated value. If neither contains $k$, then the invariant implies that $k$ is not present in the table.

Complexity

case time
worst $O(1)$
average $O(1)$

For a fixed number of hash functions, lookup checks a fixed number of cells.

Space

$$ O(m) $$

The table stores entries directly. Practical cuckoo hash tables often keep the load factor below a chosen threshold to avoid insertion cycles and frequent rebuilds.

When to Use

Cuckoo hashing lookup is appropriate when:

  • worst case constant lookup time matters
  • lookups are much more frequent than insertions
  • predictable memory access is useful
  • the implementation can tolerate more complex insertion logic

It is less convenient when insertions are extremely frequent or when resizing and rehashing must be avoided.

Implementation

class CuckooHashTable:
    def __init__(self, m=16):
        self.m = m
        self.table = [None] * m

    def _h1(self, key):
        return hash(("h1", key)) % self.m

    def _h2(self, key):
        return hash(("h2", key)) % self.m

    def get(self, key):
        i = self._h1(key)
        if self.table[i] is not None:
            k, v = self.table[i]
            if k == key:
                return v

        j = self._h2(key)
        if self.table[j] is not None:
            k, v = self.table[j]
            if k == key:
                return v

        return None
type Entry[K comparable, V any] struct {
	Key   K
	Value V
	Used  bool
}

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

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

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

func (h *CuckooHashTable[K, V]) Get(key K) (V, bool) {
	i := h.h1(key)
	if h.Table[i].Used && h.Table[i].Key == key {
		return h.Table[i].Value, true
	}

	j := h.h2(key)
	if h.Table[j].Used && h.Table[j].Key == key {
		return h.Table[j].Value, true
	}

	var zero V
	return zero, false
}