Hash Functions
Imagine building a symbol table for a compiler. It must look up thousands of variable names. Or imagine a cache that must locate a web object among billions of possible URLs. Storing every possible key in an array indexed directly by its value is impossible. The universe of keys is astronomically larger than the memory available. Yet we still want near-instant lookup, insertion, and deletion. The tool that resolves this tension is the hash function. It is a deterministic map that compresses a huge, unstructured key space down to a small range of array indices. Hash functions are the invisible machinery behind nearly every fast lookup system in computing. This includes language runtimes' dictionaries and sets, database indexes, distributed caches, content-addressed storage, routers doing packet classification, and cryptographic fingerprinting. Their design determines whether a hash table behaves like a dream — constant-time operations. Or it degrades into a nightmare of linked-list-like linear scans. Getting hashing right is a surprisingly subtle problem. A function that looks "random enough" by eye can still be adversarially exploitable. This can cause catastrophic collisions. This article isolates the hash function itself. It is treated separately from the collision-resolution schemes (Hash Tables and Direct Addressing, Separate Chaining Collision Resolution, Open Addressing Collision Resolution) that consume its output. We study the two classical deterministic constructions — the division method and the multiplication method. Then we study the theoretical framework that explains why randomized families of hash functions give provable guarantees. These families are known as universal hashing. They offer average-case guarantees even against adversarial inputs. Underlying all analysis is an idealization called the simple uniform hashing assumption. We state this assumption precisely and use it honestly. We flag exactly where it does and does not hold in practice. By the end, you will be able to design a hash function for a given key space. You will be able to reason about why a particular table size is a poor or good choice. You will understand the probabilistic guarantee that universal hashing offers. This is the same guarantee that protects real systems from algorithmic complexity attacks.
- A hash function maps keys from a huge universe U to a small range {0, …, m−1} of table slots, enabling compact storage.
- The division method uses `h(k) = k mod m`; it is fast but sensitive to bad choices of m (avoid powers of 2 with non-random keys).
- The multiplication method uses the fractional part of `k·A`, is less sensitive to m, and underlies Knuth's multiplicative hashing.
- Simple uniform hashing is the idealizing assumption that each key hashes independently and uniformly over all slots — real functions only approximate this.
- Universal hashing picks a function at random from a family where any two distinct keys collide with probability at most 1/m, giving expected-case guarantees against any input, even adversarial ones.
- Hash functions are the foundation beneath Hash Tables and Direct Addressing and every collision-resolution strategy, and they reappear inside Tries-adjacent structures like hash tries and Bloom filters.
A good hash function turns an enormous, structured key space into indices that behave as if chosen independently and uniformly at random, so that collisions become rare accidents rather than exploitable patterns.
The idea
A hash function is a function
mapping a universe of possible keys (integers, strings, records — anything) to a small integer range of size , the number of slots in a hash table. Because is typically vastly larger than , the pigeonhole principle guarantees that distinct keys will sometimes map to the same slot; this is a collision. Hash functions do not eliminate collisions — that is provably impossible when — but a good one makes collisions rare and, crucially, unpredictable to anyone trying to trigger them deliberately. Before hashing, keys are usually reduced to non-negative integers via some canonical encoding (e.g., treating a string as a number in a large base, or interpreting bytes as an integer). This article assumes that step is done and focuses on the map from integers to slot indices. Two deterministic constructions dominate practice: the division method, , and the multiplication method, which uses the fractional part of for a well-chosen constant . Both are cheap to compute — a handful of arithmetic operations — which matters because the hash function is evaluated on every table operation. The theoretical backbone for analyzing these (and any) hash functions is the simple uniform hashing assumption (SUHA): each key is equally likely to hash to any of the slots, independently of where other keys hash. Under SUHA, standard hash table analyses show expected operations. SUHA is an idealization no fixed deterministic function can truly satisfy against an adversary who knows the function, because for any fixed there exists a set of keys that all collide. This gap between idealization and reality motivates universal hashing: instead of committing to one fixed function, the table's implementation picks a function at random, at initialization time, from a carefully designed family . As long as the family is universal — any two distinct keys collide under a randomly chosen with probability at most — SUHA-like guarantees hold in expectation over the algorithm's own randomness, for any fixed sequence of keys, even one designed by an adversary who does not know which was drawn.
How it works
The division method. Given table size , define
This requires one modulus operation — extremely fast on real hardware. Its Achilles' heel is that it is only as "random-looking" as the low-order structure of allows. If , then depends only on the low bits of , ignoring all higher-order bits entirely; keys that share low bits (common with, e.g., pointers aligned to word boundaries, or IDs padded with trailing zeros) collide systematically. The standard fix is to choose to be a prime not close to a power of 2 or 10, which spreads dependence across all bits of typical keys.
The multiplication method. Choose a constant with . Define
where denotes the fractional part of . Intuitively: multiply the key by , throw away the integer part, keep the fractional part (a number in ), and scale it up to the range . This method is much less sensitive to the choice of — powers of 2 are fine, which is convenient because computing for is just a bit shift. Knuth recommends
the reciprocal of the golden ratio, because multiplying by it tends to scatter the resulting fractional parts fairly evenly, avoiding clumping patterns that some values of produce with structured input. In practice, on a machine with -bit words, this is implemented entirely with integer arithmetic: pick an odd integer constant approximating , compute the -bit product (allowing overflow to bits), and extract the top bits of the low -bit half (for ) as the hash. This avoids floating point entirely and is extremely fast — a single integer multiply and a shift.
Universal hashing. Fix a prime larger than any key value. A classical universal family (due to Carter and Wegman) is
To use it: pick and uniformly at random once, when the table is created (not per key), then use the resulting fixed function for every operation on that table instance. The randomness is in which function gets chosen, not in re-randomizing per lookup. ```text // One-time setup when the hash table is constructed: UNIVERSAL-HASH-INIT(m, p): a ← random integer in [1, p-1] b ← random integer in [0, p-1] return (a, b) // defines h_{a,b} for the table's lifetime // Evaluated on every insert / search / delete: UNIVERSAL-HASH(k, a, b, p, m): return ((a k + b) mod p) mod m ``` ```text // Division method DIVISION-HASH(k, m): return k mod m // Multiplication method (Knuth's constant, integer arithmetic on w-bit words) // s is a precomputed odd integer approximating A 2^w, m = 2^p MULTIPLICATION-HASH(k, s, w, p): product ← (k * s) mod 2^w // keep only the low w bits of the product return product >> (w - p) // top p bits of that low half ``` The key structural fact used later in the correctness argument: for and two distinct keys both less than , the map is a bijection on for each fixed nonzero — this is where the collision-probability bound comes from.
Complexity & correctness
Time cost of the hash function itself. Both the division method and the multiplication method compute in time — one modulus/multiply plus a few shifts, independent of key size (for fixed-width machine integers) or logarithmic in key size for arbitrary-precision keys. This constant-time evaluation is what allows hash table operations (insertion, search, deletion) to be on average, since each operation performs a small constant number of hash evaluations plus work proportional to the resulting slot's chain length or probe sequence — topics developed fully in Separate Chaining Collision Resolution and Open Addressing Collision Resolution.
Why SUHA gives expected time. Under simple uniform hashing, each of keys lands independently and uniformly in one of slots. For separate chaining, the expected chain length at any slot is the load factor , so an unsuccessful search expects to examine elements: for computing the hash, and for scanning the expected chain length. Keeping (e.g., resizing the table when exceeds a constant threshold) yields expected operations. The catch, stated honestly: SUHA is unverifiable and generally false for any fixed hash function, because for any deterministic , an adversary who knows can compute or more keys that all hash to the same slot — simply invert on a single target slot value repeatedly. This is not a hypothetical: it is the basis of real denial-of-service attacks against naively hashed web-request parameters and form fields, which is precisely why security-sensitive services randomize their hash seed.
Why universal hashing repairs this. Universal hashing sidesteps the adversary problem by making the function itself the random variable, chosen after (or independent of) the adversary's choice of keys. Formally, a family is universal if for every pair of distinct keys ,
Proof that the Carter–Wegman family is universal. Fix distinct keys in , with prime and every key and . Define and for drawn uniformly from and uniformly from . 1. For any fixed nonzero , the map is injective as ranges over : solving and simultaneously gives . Since is prime and , is invertible mod , so for each nonzero there is exactly one value of , hence — ranging over all pairs uniformly — the pair is uniformly distributed over all ordered pairs of distinct values in . 2. So exactly (they are forced distinct whenever ), and is uniform over the distinct ordered pairs. 3. We care about , not itself. Fixing , the number of values with is at most (a standard counting bound on residues mod within a range of size ). Dividing by the available choices of gives
This establishes universality.
Consequence for expected chain length. With a universal family, for any fixed sequence of keys (chosen with no knowledge of ), and a random used to build the table, the expected number of collisions any single key experiences with the other keys is at most — by linearity of expectation, summing the pairwise collision probability over all other keys. So expected chain length is , matching the SUHA guarantee, but now proved rigorously for any input, with the randomness coming only from the table's own coin flips, not from any assumption about the keys.
Space. A hash function itself uses extra space (a constant number of parameters: , or /, or ). This is separate from the space used by the resulting table with chaining, or with open addressing.
Honest tradeoffs. The division method is simplest and fastest to reason about but forces careful, often prime, choices of . The multiplication method tolerates power-of-two table sizes (convenient for fast modular arithmetic and cache-line alignment) but the quality of scattering depends on the choice of constant , and poor choices can still produce visible patterns. Universal hashing gives the strongest theoretical guarantee — worst-case-input resilience — but at the cost of a slightly more expensive hash evaluation (a multiply, an add, and two modular reductions) and the operational requirement of generating and storing fresh random parameters per table instance.
Worked example
Take table size and keys representing user IDs: .
Division method, :
| key | slot | |
|---|---|---|
| 18 | 18 = 2·8 + 2 | 2 |
| 41 | 41 = 5·8 + 1 | 1 |
| 22 | 22 = 2·8 + 6 | 6 |
| 44 | 44 = 5·8 + 4 | 4 |
| 59 | 59 = 7·8 + 3 | 3 |
| 32 | 32 = 4·8 + 0 | 0 |
With , the hash only depends on , i.e., the low 3 bits of . Here it happens to scatter nicely since the low bits of these particular keys vary. But notice: any key set that's arithmetic with common stride 8 (e.g., IDs — very plausible if IDs are assigned in blocks per shard) would all collide in slot 0. This is the systematic weakness of power-of-two divisors.
Multiplication method, using , : Compute , then multiply by 8 and floor.
| key | frac() | frac | slot = ⌊·⌋ | |
|---|---|---|---|---|
| 18 | 11.1246 | 0.1246 | 0.9968 | 0 |
| 41 | 25.339 | 0.339 | 2.712 | 2 |
| 22 | 13.597 | 0.597 | 4.776 | 4 |
| 44 | 27.194 | 0.194 | 1.552 | 1 |
| 59 | 36.464 | 0.464 | 3.712 | 3 |
| 32 | 19.777 | 0.777 | 6.216 | 6 |
Now the arithmetic-progression weakness is broken: 18 and 32 no longer collide, and the mapping depends on the full magnitude of , not just its low bits — because multiplying by an irrational-like constant mixes all the bits of into the fractional part.
Universal hashing, with (a prime bigger than all keys), , and a randomly drawn : .
| key | (slot) | ||
|---|---|---|---|
| 18 | 671 | 671 − 6·101 = 65 | 65 mod 8 = 1 |
| 41 | 1522 | 1522 − 15·101 = 7 | 7 |
| 22 | 819 | 819 − 8·101 = 11 | 3 |
| 44 | 1633 | 1633 − 16·101 = 17 | 1 |
| 59 | 2188 | 2188 − 21·101 = 67 | 3 |
| 32 | 1189 | 1189 − 11·101 = 78 | 6 |
Here 18 and 44 collide (both slot 1), and 22 and 59 collide (both slot 3) — collisions do happen, as guaranteed to happen sometimes even under universality. The point of universal hashing is not to eliminate this instance's collisions; it is that if a different pair had been drawn, a different pair of keys would likely collide instead, and no fixed adversarial key set can force collisions across all draws of . Rerunning with would scatter these same six keys entirely differently.
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Using with the division method on keys with structured low bits | `k mod m` depends only on low bits when is a power of 2 | Choose prime and not close to a power of 2 or 10, or switch to the multiplication method |
| Treating SUHA as literally true for a fixed, publicly known hash function | SUHA is an idealization; any fixed has adversarial worst-case key sets | Use universal hashing (randomize per table instance) in adversarial-facing systems |
| Reusing the same seed across all instances of a hash table type | Defeats the purpose of universal hashing — an attacker can learn the fixed seed and target it | Randomize per table instance/process, ideally from a secure RNG |
| Picking a poor multiplicative constant (e.g., rational with small denominator) | Produces visible periodic patterns instead of scattering | Use Knuth's or another constant with no short repeating binary/decimal pattern |
| Forgetting that hashing strings/composite keys requires a separate reduction step before applying | The division/multiplication methods assume an integer input; naive string-to-int encodings can introduce bias | Use a well-mixed integer encoding (e.g., polynomial rolling hash) before applying the table hash function |
| Confusing hash functions with cryptographic hash functions | Both "hash," but table hash functions need only speed and statistical spread, not preimage resistance | Use fast non-cryptographic hashes for tables; reserve cryptographic hashes (SHA-256, etc.) for security-critical fingerprinting |
Pioneers & history
The division and multiplication methods for hashing were systematized and popularized in Donald Knuth's The Art of Computer Programming, which analyzed their statistical behavior in depth and recommended the golden-ratio constant for multiplicative hashing. The theoretical leap — proving that randomizing over a family of hash functions gives guarantees independent of the input — came from J. Lawrence Carter and Mark N. Wegman, who introduced universal hashing in 1977, founding a rigorous probabilistic theory of hash function design that underlies modern hash table implementations, Bloom filters, and randomized data-structure security.
Origins · 1 minMark N. Wegman and how this was discovered
Wegman's work with Carter showed that randomness could be engineered directly into the design of a hash function family, rather than merely hoped for in its output — a shift from "does this function look random" to "what is the provable probability of collision over my own random choice."
Where this matters
Hash functions are the load-bearing layer beneath Hash Tables and Direct Addressing, and every collision-resolution strategy — Separate Chaining Collision Resolution and Open Addressing Collision Resolution — depends entirely on the quality of the underlying hash function for its average-case performance claims to hold. Language runtimes (Python's `dict`, Java's `HashMap`, Go's `map`) all randomize their internal string hash seed at process startup specifically to defend against algorithmic complexity attacks, a direct engineering application of universal hashing's guarantee. Database systems use hash functions for hash joins, hash partitioning, and hash indexes, where a poor hash function can cause severe data skew across partitions. Distributed systems use hashing for consistent hashing in load balancers and distributed caches (e.g., content delivery networks and key-value stores), where the hash function determines which server or shard owns a given key. Networking hardware uses hash functions for flow classification and load balancing across multiple links. Bloom filters and their relatives, and hash-based variants of Tries (hash tries, hashed compressed tries), rely on well-distributed hash functions to keep false-positive rates and lookup times low. Cryptographic contexts use a stricter cousin of these ideas — hash functions with preimage and collision resistance — but the combinatorial core (mapping a large universe into a small range while controlling collisions) is the same idea this article develops in its simplest, fastest, non-adversarial-by-default form.
Ask the tutor: How does a hash function's design choice affect which collision-resolution scheme — chaining or open addressing — performs better in practice?
Why can't we just make the hash function itself perfectly collision-free?
A collision-free (injective) function from a universe into a smaller range is mathematically impossible whenever , by the pigeonhole principle — some two keys must map to the same slot. This is true no matter how clever the function is. Hashing therefore accepts collisions as inevitable and instead focuses on two goals: making collisions statistically rare for typical inputs (via good scattering, as in the multiplication method) and making them unpredictable to an adversary who wants to force many keys into one slot (via universal hashing's randomization). Perfect hashing exists only for a known, fixed key set decided in advance, which is a separate, more specialized technique not applicable to dynamic tables receiving arbitrary future keys.
Is universal hashing actually used in real systems, or is it mostly theoretical?
It is genuinely used, though often in adapted forms. Several mainstream language runtimes randomize their string hash function's seed at process startup — effectively drawing one function from a family at random — specifically to prevent algorithmic complexity attacks where an adversary sends inputs engineered to collide under a known, fixed hash function. The Carter–Wegman construction itself, or close relatives, appears in networking hardware for flow hashing, in hash table libraries hardened against denial-of-service, and in randomized data structure research (e.g., analyses of cuckoo hashing and Bloom filters lean on universal or near-universal hash families for their probabilistic guarantees).
How do you choose the table size m in practice?
For the division method, choose prime, and ideally not close to a power of 2 or a power of 10, since real-world key sets often have structure aligned to those bases (memory addresses, decimal identifiers). For the multiplication method, is commonly chosen as a power of 2, since extracting the top bits of a multiplied word is a fast bit-shift operation, and the multiplication step itself does the work of scattering that the division method would otherwise need a prime for. In both cases, is also chosen dynamically as the table resizes to keep the load factor bounded by a small constant, which is what keeps operations expected regardless of which hashing method is used.
What is the difference between a hash function for hash tables and a cryptographic hash function like SHA-256?
Both compress a large input space into a smaller, fixed-size output, but their design goals differ sharply. A table hash function needs speed and good statistical distribution over typical or randomized inputs; it need not resist a determined adversary trying to invert it or find two specific colliding inputs by brute-force computation. A cryptographic hash function must additionally be resistant to preimage attacks (hard to find an input producing a given output), second-preimage attacks, and collision attacks (hard to find any two inputs that collide) even under heavy adversarial computational effort. Cryptographic hashes are far more expensive per evaluation, so using one inside a hot-path hash table would needlessly sacrifice performance; conversely, using a plain table hash function for security-critical fingerprinting would be unsafe.
Does universal hashing eliminate the need to resize the hash table as it grows?
No — universal hashing addresses the distribution of keys across slots, not the total count of keys relative to slots. As more keys are inserted, the load factor grows regardless of how well-distributed the hashing is, and once becomes large, expected chain lengths or probe sequence lengths grow proportionally, degrading performance even under perfect uniform hashing. Resizing (rehashing into a larger table once crosses a threshold) remains necessary; universal hashing simply guarantees that, for whatever load factor currently holds, the keys are spread close to evenly across the current table's slots, protecting against adversarial clustering rather than against natural growth.
Why does the proof of universality require p to be prime?
Primality is what makes invertible modulo whenever and both are less than . That invertibility is exactly what forces the map to be a bijection on nonzero residues, which in turn guarantees that the pair before the final mod- reduction is uniformly distributed over all distinct ordered pairs in . If were composite, some nonzero values of would share a common factor with , breaking invertibility, and the resulting distribution over would no longer be uniform — the clean 1/m collision bound would no longer follow from the counting argument.
Can a hash function be "too random," in a way that hurts performance?
Not in terms of collision behavior, but there is a cost tradeoff. Perfectly uniform-looking output typically requires more mixing work (more multiplications, more bit rotations) than a cheap function like plain division, so an extremely elaborate mixing function can slow down every single table operation even though it improves worst-case collision resistance. In most non-adversarial applications, a simple, fast multiplication-method hash is perfectly adequate; the extra cost of full universal hashing or cryptographic-strength mixing is worth paying only when inputs may be adversarial, such as public-facing services parsing untrusted keys.