Worked examples — Rabin-Karp — rolling hash, O(n+m) expected
This is a hands-on companion to the Rabin-Karp topic note — see the parent, Rabin-Karp — rolling hash, where we derived the rolling hash. Here we drill it until no case can surprise you. Before we start, one reminder of every symbol we will use — nothing new, but let us re-anchor it so line one is readable.
Everything below rides on the two boxed facts from the parent:
The scenario matrix
Every problem Rabin-Karp can throw at you falls into one of these cells. The worked examples below are labelled with the cell(s) they cover, and together they hit all of them.
| # | Case class | What is special about it | Covered by |
|---|---|---|---|
| C1 | Ordinary match | pattern occurs once, hashes agree, verify passes | Ex 1 |
| C2 | No match anywhere | every window hash differs from | Ex 2 |
| C3 | Multiple / overlapping matches | pattern occurs more than once, windows overlap | Ex 3 |
| C4 | False positive (collision) | hashes agree but strings differ → verify must reject | Ex 4 |
| C5 | Degenerate sizes | (empty), , , or (empty answer) | Ex 5 |
| C6 | Negative-value trap | subtraction goes below zero under mod | Ex 6 |
| C7 | Adversarial worst case | crafted input where every window collides → | Ex 7 |
| C8 | Real-world word problem | plagiarism / DNA style, choosing | Ex 8 |
| C9 | Exam twist | "why is prime?" / off-by-one in | Ex 9 |
Example 1 — Ordinary single match (cell C1)
Forecast: where do you expect the match — and at which index does the window "cd" begin? Jot it before reading.
Figure (s01) — what it shows: the text a c d drawn as three indexed boxes; a blue box outlines window 0 = "ac" (hash 2) and a pink box outlines window 1 = "cd" (hash 55), with a pink arrow showing the slide from window 0 to window 1 and the note that window 1's hash equals .

- Compute . Read "cd" as a base-26 number: . Why this step? The pattern's fingerprint is fixed; compute it once and compare against every window.
- Compute window 0 = "ac". . Why? This is our starting window; we must build its hash from scratch (Horner), .
- Compare. → skip instantly, no character comparison. Why? Unequal hashes prove the strings differ — that is the whole speed win.
- Roll to window 1 = "cd". Here . Why? Drop the leading 'a' (code 0), slide , add trailing 'd' (code 3) — all .
- Compare & verify. → hashes agree → compare "cd" vs "cd" char-by-char → match at index 1. Why verify? Hash equality is necessary but not sufficient (see Ex 4).
Example 2 — No match anywhere (cell C2)
Forecast: how many character-by-character verifications should happen? (Hint: think about whether any window's hash can equal .)
- . Why this step? 'b'=1, 'a'=0; we compute the pattern's fixed fingerprint once to compare against.
- Window 0 = "aa": . → skip. Why? We must build the first window's hash from scratch; unequal to means we skip in with no char check.
- Roll to window 1 = "aa": . → skip. Why? All characters identical, so every window hash is — the roll confirms it stays , still .
- No window ever equals → zero matches, zero verifications. Why this matters? It shows the best case: the hash filter rejects everything in each, never touching a single character comparison.
Example 3 — Multiple overlapping matches (cell C3)
Forecast: windows overlap here — will Rabin-Karp find index 1 even though it overlaps index 0? Predict the full list of indices.
Figure (s02) — what it shows: the text a a a a as four indexed boxes; three coloured outlines (blue, pink, yellow) mark the three overlapping length-2 windows, each labelled "hash = 0 → match" at start indices 0, 1 and 2, making visible that overlapping windows are each checked separately.

- . Why this step? Both pattern chars are 'a'=0, so the pattern fingerprint we compare against is .
- Window 0 = "aa": . Match → verify → ✓ index 0. Why? Built from scratch; hash equals , so we must verify the actual chars — they agree, so index 0 is a real match.
- Roll to window 1 = "aa": . Match → ✓ index 1. Why? Rolling (Drop-Slide-Add) keeps the hash at ; equal hash forces a verify, which passes even though this window overlaps index 0.
- Roll to window 2 = "aa": . Match → ✓ index 2. Why this matters? Rabin-Karp checks every start index independently, so overlaps are found naturally — no skipping like the failure-function idea in Knuth-Morris-Pratt.
Example 4 — False positive: a real collision (cell C4)
Forecast: and are different bytes. Can two different single characters share the same hash mod ? Guess yes/no.
- Hash of code : . Why this step? A length-1 window's hash is just its code mod ; compute both to see if they clash.
- Hash of code : . Why? Because , the two differ by exactly one multiple of , so they land on the same remainder. This is a collision.
- If a text had byte where the pattern has 'c', their hashes match ⇒ Rabin-Karp is forced to verify char-by-char. Why? Equal hashes cannot be trusted; the algorithm must fall back to the actual bytes.
- Verification compares actual bytes → correctly rejects. Why this matters? The hash is a filter, not a proof. Skipping verification would report a phantom match. The rate of such events is roughly — see Birthday Paradox for the intuition on why collisions creep in faster than you'd think.
Example 5 — Degenerate sizes, including the empty pattern (cell C5)
Forecast: In case (a) an empty pattern — where does "nothing" occur inside "dog"? In case (d) how many windows even exist? Guess before reading.
- (a) (empty pattern). By the standard convention an empty string occurs at every position , i.e. matches. There are no characters to hash, so Rabin-Karp guards this case first and returns without touching the hash machinery. Why guard? The formulas assume (there is a "leading digit" to drop). With there is no window to build; we must special-case it or the loop over and window slices is meaningless.
- (b) . Then . Each window is one char. . Windows: 'd'=3, 'o'=14, 'g'=6. Only window index 1 equals → match at 1. Why ? With one character there is nothing to shift; the leading weight is .
- (c) . Exactly window (the whole text). . , so . . Inner: . Then . : , . So both and the single window hash equal → match at 0. Why? Pattern equals the whole text, so exactly one window, and it matches.
- (d) . Number of windows . There are no windows → return "no matches" immediately, before hashing. Why guard this? Building or a window hash when no window fits is meaningless and can crash indexing.
Example 6 — The negative-value trap (cell C6)
Forecast: After we drop the big leading 'z', the intermediate value might dip below zero mod . Predict the sign before fixing.
Figure (s03) — what it shows: a chalk number line from below zero up past ; a pink arrow starts at the small reduced hash , jumps left to after subtracting the heavy leading-digit term , then a yellow arrow "adds " repeatedly (a blue bracket spanning multiples of ) to land back inside the legal band at .

- . : , . So .
- .
- Roll naively: .
Why negative? The dropped digit's weight () is far bigger than the small reduced hash (). Under mod arithmetic this is legal but a computer's
%on a negative can misbehave. - Fix — add until non-negative, then continue. In code:
((44 - 650) % q + q) % q. : since , we have . Reduced value . - Finish the roll: .
- Cross-check from scratch: . ✓ The fix gave the right answer.
Example 7 — Adversarial worst case (cell C7)
Forecast: with , how many distinct hash values can exist? Predict how often the verify step runs on this text.
Figure (s04) — what it shows: six indexed boxes all 'a'; five overlapping length-2 window brackets, every one labelled "hash = 2 = h(P) → VERIFY", with a yellow banner "only 3 buckets {0,1,2}" to show why so many windows are forced to collide.

- With every hash lives in — only 3 possible fingerprints. Why this hurts? With so few buckets, many windows are forced to share a hash (pigeonhole). See Birthday Paradox.
- . Why this step? Fix the pattern fingerprint we compare against.
- Every window of is "aa" (all characters are 'a'), so every window hash is also . Concretely window 0 = "aa" hashes ; rolling with gives , and likewise for every later window. Why? Because is a constant string, the hash never changes; here it also equals , so every window is a hash hit.
- Every one of the windows is a hit → we run the char verify 5 times. Why ? If (nearly) all windows hit, total cost — the naive cost, hashing wasted.
- Fix: pick a large prime like (or double-hash) so collision chance is negligible. Why? A big modulus spreads hashes over billions of buckets, so distinct strings almost never share one and verification is rare — see Hashing for base/prime choice.
Example 8 — Real-world word problem: plagiarism check (cell C8)
Forecast: Two documents, hashing 5-grams — will the first window of match ? Guess before computing.
- , base 2 (Horner): . So . Why base 2? Alphabet has 2 symbols; base = alphabet size keeps digits distinct.
- First window of : bits . Horner: . So . Why? Build the starting window from scratch; → not a plagiarism hit at index 0.
- Roll over to window 1 = "01010" (bits ). Here . . Why? Drop the leading '1' (weight 3), slide , add trailing '0' — the shingle update.
- Cross-check from scratch: bits : . ✓ Roll agrees. Why this matters? Real plagiarism detectors hash millions of shingles; the roll is what makes it feasible — same idea as String Hashing for Substring Comparison.
Example 9 — Exam twist: off-by-one in (cell C9)
Forecast: the leading character of a length- window carries weight . Predict the exponent before reading, then see when the wrong exponent stays hidden.
- Correct weight is . In , the characters carry exponents down to ; the leading one is . For : . Why not ? There are digit slots for exponents ; the highest available is . Using invents a slot that does not exist.
- Correct roll (recap of Ex 1). . ✓ Why? This is the reference answer we compare the bug against.
- Buggy roll on "acd". With : Why does the bug hide here? The dropped character is 'a', so regardless of the weight — the wrong is multiplied by zero and vanishes. Graders love inputs like this because the bug is invisible.
- Expose the bug — dropped char non-zero. Reuse the "za"→"ab" roll from Ex 6, where . Correct drop term: . Buggy drop term: ; , so . Why this proves it? : the subtraction is different, so comes out wrong — real matches get missed or phantom hits appear. The bug only ever hides when the dropped char is .
- Why must be prime? A prime makes the multiplicative structure of well-behaved (no small factors that many strings share), so hashes spread evenly and collisions stay near . A composite can let whole families of strings collapse to the same residue. See Modular Arithmetic. Why the exam asks this? It checks you understand controls collision spread, not just "keeps numbers small".
Wrap-up: which cell did each hit?
Connections
- Parent topic — the derivation
- Hinglish version
- Hashing — why large primes; base choice
- Modular Arithmetic — the
+qfix, remainders - Birthday Paradox — why collisions arrive sooner than expected
- String Hashing for Substring Comparison — same roll for range equality
- Knuth-Morris-Pratt · Z-Algorithm — collision-free linear alternatives