The mathematics behind string hashing: why it works, how likely a collision is, and how to choose the parameters. (For the code and the practical recipe, see String Hashing.)

The polynomial view

Treat a string of length as the polynomial

and define for a chosen evaluation point .

Two strings collide exactly when

So a collision means is a root of the difference polynomial.

The collision bound

is a non-zero polynomial of degree over the field (assuming prime). A non-zero polynomial of degree has at most roots. Therefore, for a uniformly random :

This is the Schwartz-Zippel lemma in one variable, and it is the entire justification for hashing.

Over comparisons, a union bound gives . With , and , that is about — safe.

Why randomising matters

The bound above requires to be random and unknown to the adversary. With a fixed , a problem setter can:

  • precompute a colliding pair by a birthday attack in work, offline, once;
  • for (unsigned overflow), construct the Thue-Morse sequence, which collides for every base in length.

Randomising at runtime moves the adversary from “precompute one counterexample” to “guess the seed”, which is infeasible.

Birthday paradox — the other failure mode

Even with a random , if you insert distinct strings into a hash set, the chance that some pair collides is roughly

for a 1% collision chance
double hashing ()

This is why is not enough when you hash or more distinct substrings: gives a collision probability near 1. Use or double hashing.

Choosing the modulus

Verdict
(unsigned overflow)never — Thue-Morse breaks it deterministically
too small for many distinct strings; fine for a single pattern match
too small
the right default — Mersenne prime, fast reduction, huge
two primes near equivalent strength, two multiplications

Fast reduction modulo

static const unsigned long long M = (1ULL << 61) - 1;
unsigned long long mul(unsigned long long a, unsigned long long b) {
    __uint128_t r = (__uint128_t)a * b;
    unsigned long long lo = (unsigned long long)(r & M), hi = (unsigned long long)(r >> 61);
    unsigned long long s = lo + hi;
    return s >= M ? s - M : s;
}

Because , the high bits fold into the low ones with a shift and an add — no division.

Composability

Polynomial hashes concatenate cleanly:

That identity is what makes prefix hashing, rolling hashes, and hashing inside a segment tree all work — a segment tree node storing (hash, length) merges in , giving hashes of a string under point updates.

Beyond strings

The same argument hashes anything with a polynomial encoding:

  • Sequences of integers — same formula with the values as coefficients.
  • Multisets for a random scramble ; order-independent, and collision-resistant by the same counting argument.
  • Trees — combine children’s hashes with a commutative, scrambled operation.
  • Sets over — XOR of random 64-bit values per element (Zobrist hashing), used for board positions in game engines.

See also: String Hashing · Rolling Hash · Birthday Paradox