Hashing and Bloom Filters

Algorithmics study and revision notes

Jaak Vilo

2026-08-22

Why hashing?

A dictionary stores key-value associations and supports search, insertion, and deletion by key. Direct addressing is ideal when keys already lie in a small universe: store the value for key xx in array cell xx. When the possible key universe is enormous compared with the number of stored keys, that array wastes space.

Hashing maps a large universe into a manageable table. The price is unavoidable collisions: different keys can map to the same cell. A hash table is correct because it resolves collisions and checks actual key equality, not because its hash function somehow prevents all collisions.

Learning goals

After studying this note, you should be able to:

Prerequisites

The dictionary model

A dictionary maintains a set of entries (key,value)(key,value), normally with unique keys. Its abstract operations are:

insert_or_replace(D, key, value)
search(D, key)
delete(D, key)

The model should specify key equality and mutability. If a key changes after insertion while its stored hash position does not, later search may fail.

A hash code maps a key to a large integer, often a machine word. A separate compression step maps that integer to a table index in {0,,m1}\{0,\ldots,m-1\}. For example,

index(key)=hashcode(key)modm.index(key)=hashcode(key)\bmod m.

This separation matters when resizing: changing mm changes table indices even when hash codes remain fixed.

What a table hash function must do

Equal keys must produce equal hash codes. For expected performance, the indexed values of the anticipated keys should be spread across the table without strong patterns. Fast mixing is useful, but it is not the same goal as cryptographic preimage or collision resistance.

For composite objects, combine every equality-relevant field in a defined order. A string hash, for example, can update an integer accumulator once per character and then apply a table-specific compression or universal-hashing step. Test a proposed function on representative and adversarial-looking data; visual uniformity alone is not a proof.

Separate chaining

Each table cell stores a bucket containing all entries whose index equals that cell. A bucket may be a list, compact array, or another small dictionary.

With nn entries and mm buckets, the load factor is

α=nm.\alpha=\frac{n}{m}.

Under simple uniform hashing, a bucket has expected length α\alpha, and search, insertion, and deletion take expected O(1+α)O(1+\alpha) time. Chaining permits α>1\alpha>1 and makes deletion direct, but pointer-heavy buckets can have poor locality.

Correct search first selects the bucket, then compares the query key with each candidate’s actual key. Comparing hash codes alone is incorrect because unequal keys can share a code.

Open addressing

Open addressing stores every entry directly in the table. For key xx, a probe sequence

h(x,0),h(x,1),,h(x,m1)h(x,0),h(x,1),\ldots,h(x,m-1)

specifies cells to inspect until the key or an available cell is found.

Search must follow exactly the insertion probe sequence. Deleting an item by marking its cell empty can break a later search whose probe path crosses that cell. Use a tombstone that search passes but insertion may reuse, or rebuild the affected table.

Open addressing requires α<1\alpha<1. Under the idealised uniform-probing model, expected probes for an unsuccessful search are at most

11α,\frac{1}{1-\alpha},

and for a successful search at most

1αln11α.\frac{1}{\alpha}\ln\frac{1}{1-\alpha}.

Real linear probing has correlated probes, so these formulas are a model, not a guarantee. Performance rises sharply as the table becomes full.

Resizing and amortised time

When load passes a chosen threshold, allocate a geometrically larger table and reinsert every live entry. Entries must be reinserted because their indices depend on the new size; copying cells to equal indices is not enough.

A resize costs Θ(n)\Theta(n), but geometric growth makes a long sequence of insertions amortised O(1)O(1) under the hashing assumptions. Each entry can be charged a small amount on insertion to pay for the occasional rebuild. Tombstones may also trigger a same-size rebuild even when the live load is moderate.

Always distinguish the two probability layers:

An ordinary hash table still has Θ(n)\Theta(n) worst-case search if many keys land together.

Collision and occupancy estimates

Suppose nn distinct keys are hashed independently and uniformly into mm cells. Each unordered key pair collides with probability 1/m1/m. By linearity of expectation,

E[colliding pairs]=(n2)1m=n(n1)2m. E[\text{colliding pairs}]=\binom n2\frac1m =\frac{n(n-1)}{2m}.

For nmn\le m, the exact no-collision probability is

Pr(no collision)=i=0n1(1im). \Pr(\text{no collision}) =\prod_{i=0}^{n-1}\left(1-\frac{i}{m}\right).

When nn is small relative to mm, taking logarithms and retaining the leading term gives the birthday approximation

Pr(no collision)exp(n(n1)2m). \Pr(\text{no collision}) \approx \exp\!\left(-\frac{n(n-1)}{2m}\right).

The probability of at least one collision reaches about one half near

n2mln2.n\approx\sqrt{2m\ln2}.

For any particular cell, the probability of remaining empty is (11/m)n(1-1/m)^n. Hence

E[empty cells]=m(11m)nmen/m. E[\text{empty cells}] =m\left(1-\frac1m\right)^n \approx me^{-n/m}.

Define the reported collision metric. “Colliding pairs,” “keys beyond the first in an occupied cell,” and “number of occupied cells with load at least two” are different quantities.

Universal hashing

A fixed deterministic hash can perform badly on a carefully selected key set. Universal hashing chooses one function randomly from a family after the key set is fixed. A family \mathcal H mapping universe 𝒰\mathcal U to mm cells is universal if, for every distinct x,yx,y,

Prh[h(x)=h(y)]1m. \Pr_{h\in\mathcal H}[h(x)=h(y)]\le\frac1m.

A standard construction chooses a prime pp larger than every encoded key, chooses aa uniformly from {1,,p1}\{1,\ldots,p-1\} and bb uniformly from {0,,p1}\{0,\ldots,p-1\}, and uses

ha,b(x)=((ax+b)modp)modm. h_{a,b}(x)=((ax+b)\bmod p)\bmod m.

The random parameters are selected once per table and stored with it. For two distinct keys, the affine map modulo pp randomises their relative residues sufficiently to bound their chance of landing in the same final bucket. The guarantee is about collision probability over the function choice; it does not say one realised table has no collisions.

More explicitly, for distinct x,yx,y, the pair

((ax+b)modp,(ay+b)modp) ((ax+b)\bmod p,(ay+b)\bmod p)

is uniform over ordered pairs of distinct residues. After fixing the first residue, at most p/m1(p1)/m\lceil p/m\rceil-1\le(p-1)/m of the other p1p-1 residues have the same remainder modulo mm. Hence the collision probability is at most 1/m1/m.

Bloom filters

A Bloom filter represents approximate membership using an mm-bit array and kk hash-derived positions.

insert(x):
    for each position h_i(x): set bit h_i(x)

possibly_contains(x):
    return every bit h_i(x) is 1

If any queried bit is zero, the key is definitely absent. If all are one, it is possibly present. With consistent hashing and no unsupported deletion, inserted keys have no false negatives; unrelated keys can be false positives.

Under the standard model in which the knkn selected positions are independent and uniform, a particular bit remains zero with probability

(11m)knekn/m. \left(1-\frac1m\right)^{kn}\approx e^{-kn/m}.

Under the usual independence approximation, all kk queried positions are one with probability

p(1ekn/m)k. p\approx\left(1-e^{-kn/m}\right)^k.

For fixed bits per item m/nm/n, this expression is minimised near

k=mnln2. k=\frac mn\ln2.

With ten bits per item, k6.93k\approx6.93, so seven hashes are natural:

p(1e0.7)70.0082. p\approx(1-e^{-0.7})^7\approx0.0082.

This is a model prediction. Correlated or poorly distributed hashes can produce worse results. Compare measured false positives against the prediction on queries known not to be in the inserted set.

Practical filters often derive kk positions from two base hashes rather than compute kk unrelated hashes. This reduces hashing cost and can work well, but the independence calculation remains a model to validate empirically for the chosen scheme and data.

A standard Bloom filter cannot safely delete: clearing a bit shared with another item may create a false negative. A counting Bloom filter replaces bits with small counters, at greater space cost.

Security hashing is a different tool

These concepts share a name but solve different problems:

A password salt is a public, unique random value stored with the password verifier. It prevents identical passwords from sharing one stored result and defeats one precomputed table across many accounts. It does not turn a fast general hash into a suitable password-hashing scheme.

Using a cryptographic digest and then reducing it modulo mm can distribute table indices well, but usually costs more CPU than a noncryptographic table hash. Performance and threat model should decide whether that cost is justified.

Zobrist hashing

Search programs repeatedly modify structured states such as game boards. Zobrist hashing assigns a random machine word R[position,piece]R[position,piece] to every position-piece combination and XORs the words for all occupied positions:

H(state)=(pos,piece) occupiedR[pos,piece]. H(state)=\bigoplus_{(pos,piece)\text{ occupied}}R[pos,piece].

For a Tic-Tac-Toe move placing X in cell 4, update in constant time with

HHR[4,X].H\leftarrow H\oplus R[4,X].

Moving or replacing a piece XORs out its old contribution and XORs in its new one. The hash is an efficient fingerprint for transposition tables, not a proof of state equality; store enough state information to detect the rare collision.

Enrichment: static and similarity hashing

A perfect hash function has no collisions on one fixed key set. Two-level randomized schemes can build a static dictionary with expected linear space and construction time and worst-case constant lookup. A minimal perfect hash maps nn fixed keys into exactly nn indices, but normally needs extra storage to verify whether an arbitrary query belongs to the original set.

Locality-sensitive hashing deliberately gives similar objects an elevated chance of colliding. That goal is nearly the opposite of ordinary table hashing and is useful for approximate similarity search. It requires a defined distance or similarity family and its own probability analysis.

Worked trace: tombstones and collision estimates

Let m=7m=7, use h(x)=xmod7h(x)=x\bmod7, and resolve collisions by linear probing.

  1. Insert 10 at cell 3.
  2. Key 17 also starts at 3, so place it at 4.
  3. Key 24 starts at 3, passes occupied cells 3 and 4, and enters cell 5.
  4. Delete 17 by placing a tombstone at 4.
  5. Searching for 24 must pass cell 4 and succeeds at 5. If cell 4 had been marked empty, search would incorrectly stop.

For a separate experiment with m=100000m=100\,000 and n=1000n=1\,000 uniformly hashed keys,

E[colliding pairs]=100099921000004.995. E[\text{colliding pairs}] =\frac{1000\cdot999}{2\cdot100000} \approx4.995.

The birthday estimate predicts only e4.9950.0068e^{-4.995}\approx0.0068 probability of no collision. A collision is therefore unsurprising even though only one percent of the table’s capacity is used.

Complexity and trade-offs

Structure Lookup Insert Delete Main assumptions
Direct-address table O(1)O(1) worst case O(1)O(1) O(1)O(1) small integer universe
Chained hash table expected O(1+α)O(1+\alpha) expected O(1)O(1) amortised expected O(1+α)O(1+\alpha) distributed hashes and resizing
Open addressing expected O(1)O(1) at controlled α\alpha expected amortised O(1)O(1) expected O(1)O(1) valid probe sequence and tombstones
Balanced search tree O(logn)O(\log n) worst case O(logn)O(\log n) O(logn)O(\log n) comparable keys; preserves order
Bloom filter O(k)O(k) O(k)O(k) unsupported normally approximate membership only

Hash tables do not preserve order. Use a search tree or another ordered index for predecessor, successor, sorted iteration, and range queries.

Connections

Common mistakes

Self-check

  1. Why must a resized table reinsert rather than merely copy its cells?
  2. Trace insertion and deletion of three colliding keys under quadratic probing.
  3. Estimate the first-collision scale for a table with one million cells.
  4. Derive the expected number of empty cells from one cell’s empty probability.
  5. What randomness appears in the universal-hashing guarantee?
  6. Which Bloom-filter answer is certain, and which is probabilistic?
  7. Explain the distinct roles of a password salt and a slow password-hashing function.
  8. Why is XOR useful for Zobrist updates?

Revision summary

Implementations and hands-on exploration

Small experiments

  1. For several pairs (n,m)(n,m), choose a fresh universal hash function for each trial, insert fixed distinct integers, and record colliding pairs and empty cells. Compare the sample means with n(n1)/(2m)n(n-1)/(2m) and m(11/m)nm(1-1/m)^n.
  2. Build Bloom filters at fixed bits per item while varying kk around (m/n)ln2(m/n)\ln2. Query a disjoint held-out set, plot measured versus predicted false-positive rates, verify that inserted items have no false negatives, and then deliberately overfill one filter.

Sources and further study

References

Cormen, Thomas H., Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein. 2022. Introduction to Algorithms. 4th ed. MIT Press.