Skip to content

Linear Probing Search

Resolve hash collisions by scanning sequential slots until the key is found or an empty slot appears.

Linear probing resolves collisions in an open addressing hash table by scanning forward one slot at a time. All keys are stored directly inside the table, and probing follows a contiguous sequence.

You search by computing the hash index and walking forward until you either find the key or reach an empty slot.

Problem

Given a hash table using linear probing and a key kk, return the associated value if it exists.

Model

  • Table TT has size mm
  • Hash function h(k)h(k)
  • Probe sequence:
it=(h(k)+t)modm i_t = (h(k) + t) \bmod m

for t=0,1,2,t = 0, 1, 2, \dots

Algorithm

linear_probing_search(T, k):
    i = h(k)
    start = i
    while T[i] is occupied:
        if T[i].key == k:
            return T[i].value
        i = (i + 1) mod m
        if i == start:
            break
    return NOT_FOUND

Example

Let:

  • m=7m = 7
  • h(k)=kmod7h(k) = k \bmod 7

Insert:

[10,17,24] [10, 17, 24]

All hash to index 33, so they occupy:

indexvalue
310
417
524

Search for k=24k = 24:

  • start at index 3
  • check 10, then 17, then 24
  • match found

Correctness

Linear probing guarantees that every key is placed somewhere along its probe sequence. The lookup follows exactly the same sequence, so it will encounter the key if it exists.

An empty slot terminates the search, proving that the key does not exist.

Complexity

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

casetime
averageO(1)O(1)
worstO(n)O(n)

Performance degrades as α\alpha approaches 1 due to clustering.

Space

O(m) O(m)

All elements are stored directly in the table.

When to Use

Linear probing is appropriate when:

  • memory locality is important
  • cache performance matters
  • implementation simplicity is desired

It performs poorly under high load due to primary clustering.

Implementation

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

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

    def get(self, key):
        i = self._hash(key)
        start = i

        while self.table[i] is not None:
            k, v = self.table[i]
            if k == key:
                return v
            i = (i + 1) % self.m
            if i == start:
                break

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

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)
	start := i

	for h.Table[i].Used {
		if h.Table[i].Key == key {
			return h.Table[i].Value, true
		}
		i = (i + 1) % h.M
		if i == start {
			break
		}
	}

	var zero V
	return zero, false
}