Visualizer CodeViz · Algorithms, visualized

Part I · Hash tables · week 6

Linear probing

One key per slot: on a collision, step right until you find an empty one. Fast and compact — as long as you keep the table less than half full.

Run the animation, step by step → generated live from any input you type — nothing is pre-recorded

Cost and properties

search hit~3/2 probes at 50% load
search miss~5/2 probes at 50% load
load factormust stay < 1 (aim ≤ ½)
deletionawkward — must reinsert the cluster

Reference: Sedgewick & Wayne, §3.4; Knuth 1963.

Linear probing in Python

Runnable as-is, and written for reading. Watch the highlighted line move through it in the animation, or send it straight to the visualizer — it arrives with a test case ready to run, yours to edit.

Open in Visualizer

class LinearProbingHashST:
    # One key per slot. A collision walks RIGHT (wrapping) to the next
    # empty slot, so keys form CLUSTERS -- and long clusters are the enemy.

    def __init__(self, m=16):
        self.m = m
        self.keys = [None] * m
        self.vals = [None] * m
        self.n = 0

    def _hash(self, key):
        h = 0
        for c in str(key):
            h = (31 * h + ord(c)) & 0x7fffffff
        return h % self.m

    def put(self, key, val):
        if self.n >= self.m // 2:
            self._resize(2 * self.m)      # keep the load factor <= 1/2
        i = self._hash(key)
        while self.keys[i] is not None:   # probe until an empty slot
            if self.keys[i] == key:
                self.vals[i] = val
                return
            i = (i + 1) % self.m
        self.keys[i] = key
        self.vals[i] = val
        self.n += 1

    def get(self, key):
        i = self._hash(key)
        while self.keys[i] is not None:
            if self.keys[i] == key:
                return self.vals[i]
            i = (i + 1) % self.m
        return None                       # an empty slot proves absence

    def _resize(self, capacity):
        old_keys, old_vals = self.keys, self.vals
        self.m, self.n = capacity, 0
        self.keys = [None] * capacity
        self.vals = [None] * capacity
        for k, v in zip(old_keys, old_vals):
            if k is not None:
                self.put(k, v)            # rehash: i changes with m

Why it works

An empty slot is a proof

Search probes right until it finds the key or an empty slot. The empty slot is what makes the miss correct: if the key existed it would have been placed at the first empty slot in this run, so an empty slot means absence. Everything about linear probing follows from that one property — including why deletion is hard.

Clustering, and Knuth's result

Contiguous runs of occupied slots (clusters) merge as the table fills, and a long cluster makes every key hashing into it slow. Knuth's 1962 analysis: with load factor α, a search hit costs ~½(1 + 1/(1−α)) probes and a miss ~½(1 + 1/(1−α)2). At α = ½ that is 1.5 and 2.5 probes; at α = 0.9 it is 5.5 and 50.5. The cost does not degrade gracefully — it explodes. Hence the resize at half full.

That analysis was, in Knuth's own telling, the result that convinced him analysis of algorithms was worth a career.

Resizing rehashes everything

i = hash(key) % m depends on m, so doubling the table means every key moves — you cannot copy the array. Watch the resize frames: the keys land in completely different slots. Amortised this is still constant time per operation (same argument as the resizing-array stack), but any single insert can be linear.

Why deletion is awkward

Simply blanking a slot breaks the empty-slot proof: keys past the hole become unreachable. The fix is to remove the key and reinsert every key in the rest of its cluster. That is why hash tables that need frequent deletion often use chaining instead — or tombstones, which slowly poison the table.

What to try in the animation

Set m small (say 8) to watch clusters merge and probe counts climb — then watch the automatic resize rescue it.

The animation is generated from the input box, in your browser — press Animate after editing it. To execute the Python itself, use Open in Visualizer above.

Open Linear probing in the player →

The rest of Hash tables