Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Hash the pattern once, then roll a fixed-width hash across the text so each window's hash updates in O(1) instead of recomputing from scratch. When a window's hash equals the pattern's hash, verify character-by-character to rule out a collision (a 'spurious hit'). Average and best case O(n + m); worst case O(n·m) if hashes collide constantly, which a good modulus and base make vanishingly unlikely.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you understand that hashing gives a fast probabilistic filter that still needs verification, and whether you can derive the O(1) rolling update with correct modular arithmetic.
Where candidates slip
Reporting a match on hash equality without verifying characters (false positive on a collision), or botching the modular subtraction so values go negative.
How to stand out
Name the spurious-hit risk explicitly, state the O(n+m) average vs O(n·m) worst case honestly, and mention double hashing as the mitigation.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewWindow hash== pattern hash?NoYesExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Naive — Recompute Each Window
1Hint 1Explore hintHide hint
For each start s from 0 to n−m, compare the window t[s..s+m-1] with p one character at a time.
2Hint 2Explore hintHide hint
Each comparison is up to m characters, and there are about n windows → n·m work.
3Hint 3Explore hintHide hint
Notice consecutive windows share m−1 characters — recomputing everything ignores that overlap.
A · Core idea
The baseline simply re-derives whether each window equals the pattern from scratch.
The literal method. Slide a start index s across the text, and at each s compare the m characters of the window with the pattern. Stop early on the first mismatch; record s if all m match.
The overlap it ignores. Window starting at s and window starting at s+1 share m−1 characters. The naive method re-reads those shared characters every step. A rolling hash captures the window in a single number so that shifting reuses almost all prior work.
Worst case. Text "aaaa...a" with pattern "aaa...ab" matches m−1 characters per window before failing — n windows × m checks ≈ n·m. At n = m = 10⁶ that's 10¹² operations, far too slow.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
t = "abcab", p = "cab"Array state
s=2
"cab" matches the pattern
[2]C · Code with teaching comments
Reference implementation previewimport java.util.ArrayList; import java.util.List; public class NaiveWindow { public List<Integer> search(String t, String p) {Explore the full codeHide the full code
D · Why it works and complexityO(n·m) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The redundancy is the m−1 overlapping characters between adjacent windows. Rabin–Karp's rolling hash is exactly a way to update the window's fingerprint in O(1) instead of rereading that overlap.
ComplexityO(n·m) time, O(1) space — Up to n−m+1 windows, each compared in up to m character checks → O(n·m). Constant extra space.
E · Edge cases and pitfalls3Looping s past n−m → out-of-bounds.Explore edge casesHide edge cases
Pitfalls
- Looping s past n−m → out-of-bounds.
- Treating it as acceptable at large n — adversarial inputs are genuinely quadratic.
- Not early-exiting the inner loop on mismatch (still O(n·m) but slower constant).
Approach 2 of 2 · Optimal
Rolling Hash (Optimal Average Case)
1Hint 1Explore hintHide hint
Treat a length-m string as a base-b number mod M: hash = s[0]*b^{m-1} + ... + s[m-1]. Equal strings always share this hash.
2Hint 2Explore hintHide hint
When the window slides one step right, the new hash is (old hash minus the leaving char's high-order term) times b, plus the entering char — an O(1) update if you precompute b^{m-1} mod M.
3Hint 3Explore hintHide hint
A matching hash might be a coincidence (collision). Verify the m characters before recording, and you stay correct even when hashes collide.
A · Core idea
Rabin–Karp encodes each fixed-width window as a polynomial hash and slides that hash across the text in constant time per step.
The hash as a base-b number. Read the m characters of a window as digits of a base-b number taken modulo a large prime M: hash = c0·b^{m-1} + c1·b^{m-2} + … + c_{m-1}. Two windows that are character-for-character equal always produce the same hash. Two different windows usually produce different hashes, but not always — that 'not always' is the collision you must handle.
The O(1) roll — the core trick. When the window moves from [i, i+m-1] to [i+1, i+m], you want the new hash without recomputing the sum. Subtract the outgoing character's contribution c_i · b^{m-1}, multiply the rest by b (shifting every digit up one place), then add the incoming character c_{i+m}, all mod M. Precomputing b^{m-1} mod M once makes this three arithmetic operations regardless of m.
Spurious hits and why verification is mandatory. A hash equal to the pattern's hash is a candidate, not a guarantee. Because many strings map into the same residue class mod M, two unequal windows can share a hash — a 'spurious hit'. So on every hash match, compare the actual m characters. With a large prime M and base b above the alphabet, spurious hits are rare, so verification rarely fires and the average cost stays linear. But you can never skip it without risking a wrong answer.
Modular hygiene. Subtraction can make the intermediate value negative; add M before the final mod so the stored hash stays in [0, M). In languages with fixed-width integers, also keep the base and modulus small enough that hash·b doesn't overflow (use 64-bit, or a modulus near 10⁹).
Honest complexity. Average and best case are O(n + m). The worst case is O(n·m) when hashes collide on (almost) every window — possible if an adversary engineers the input against a known fixed base/modulus. Randomizing the base or using two independent hashes (double hashing) drives the collision probability to negligible levels.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
t = "abcab", p = "cab", b = 256, M = 101Array state
window [0,2]
hash("abc")
[2]C · Code with teaching comments
D · Why it works and complexityO(n + m) average, O(n·m) worst timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
A polynomial hash makes 'are these two length-m strings equal?' an O(1) question, and the rolling update makes 'advance the window' O(1) too. The price is that equality becomes probabilistic — so you bolt on a cheap O(m) verification that runs only on the rare hash match, recovering exactness without losing the average-case speed.
ComplexityO(n + m) average, O(n·m) worst time, O(1) space — Computing the pattern hash and the first window hash is O(m). Each of the ~n rolls is O(1). Verifications are O(m) but happen only on hash matches, which are rare with a good modulus → O(n + m) expected. If every window collides (adversarial), every verification costs O(m) → O(n·m) worst case.
E · Edge cases and pitfalls7t = "aaaaa", p = "aa" — Adjacent windows all hash equal and verify → overlapping matches [0,1,2,3].Explore edge casesHide edge cases
Edge cases handled
t = "aaaaa", p = "aa"Adjacent windows all hash equal and verify → overlapping matches [0,1,2,3].p longer than tEarly return empty before hashing.engineered collisionsVerification still keeps the answer correct; only the time degrades toward O(n·m). Double hashing mitigates.
Pitfalls
- Reporting a match on hash equality without verifying characters — wrong on collisions.
- Letting the rolling hash go negative after subtraction — add MOD before the final mod (matters in Java).
- Integer overflow when MOD·BASE exceeds the type — use 64-bit and a modulus near 10⁹.
- Using a tiny modulus 'to keep numbers small' — collisions become frequent and you drift toward O(n·m).
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewimport java.util.ArrayList; import java.util.List; public class RabinKarp { private static final long BASE = 256;Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1No verification after a hash match
Different strings can share a hash (spurious hit). Reporting on hash equality alone returns wrong indices whenever a collision occurs.
Explore the code comparisonHide the code comparison
No verification after a hash match
Different strings can share a hash (spurious hit). Reporting on hash equality alone returns wrong indices whenever a collision occurs.
Don't write this
if w_hash == p_hash:
result.append(i) # WRONG: collision => false positiveWrite this instead
if w_hash == p_hash and t[i:i + m] == p:
result.append(i)2Negative hash after the roll
In fixed-width-int languages, subtracting the outgoing term can make the hash negative, corrupting later comparisons. Add MOD before taking mod again.
Explore the code comparisonHide the code comparison
Negative hash after the roll
In fixed-width-int languages, subtracting the outgoing term can make the hash negative, corrupting later comparisons. Add MOD before taking mod again.
Don't write this
wHash = (wHash - t.charAt(i) * highOrder % MOD) % MOD; // may be negativeWrite this instead
wHash = (wHash - t.charAt(i) * highOrder % MOD + MOD) % MOD;3Modulus too small
A small M means many strings collide, so verification fires constantly and the algorithm slides toward its O(n·m) worst case while also risking overflow-free but useless hashes.
Explore the code comparisonHide the code comparison
Modulus too small
A small M means many strings collide, so verification fires constantly and the algorithm slides toward its O(n·m) worst case while also risking overflow-free but useless hashes.
Don't write this
MOD = 101 # collisions everywhereWrite this instead
MOD = 1_000_000_007 # large prime keeps spurious hits rareRevise
Carry the pattern into the interview
Pattern to remember
Rolling polynomial hash over a sliding fixed-width window
Code pattern to memorize
hash(s[0..m-1]) = (s[0]*b^{m-1} + s[1]*b^{m-2} + ... + s[m-1]) mod M
roll forward by one:
h = ( (h - s[i]*b^{m-1}) * b + s[i+m] ) mod M (keep non-negative)
on hash match: VERIFY characters before reportingRules to remember
- 1A hash match is necessary, not sufficient — always verify characters to reject spurious hits.
- 2Precompute
b^{m-1} mod Monce; the roll uses it to subtract the outgoing character's contribution. - 3Pick a large prime modulus
Mand a baseblarger than the alphabet to keep collisions rare. - 4Take
modafter every operation and addMbefore anothermodto keep values non-negative (Java/Python int overflow vs negative quirks differ). - 5Worst case is
O(n·m)under adversarial collisions — say this out loud; double-hashing makes it astronomically unlikely.
Use this pattern when
- Substring search where average-case linear time is acceptable
- Comparing many equal-length substrings (precomputed hashes give O(1) equality checks)
- Detecting duplicate substrings of a fixed length (e.g. Repeated DNA Sequences)
- Plagiarism / fingerprinting style problems over sliding windows
Choose another pattern when
- You need a guaranteed worst-case linear bound → KMP or Z-algorithm
- Multiple patterns at once → Aho–Corasick
- Tiny inputs where built-in find suffices → don't hand-roll hashing
- Adversary controls the input and can engineer collisions → randomize base or use double hashing, or switch to KMP
Interview takeaway
Rabin–Karp trades exactness for speed: a rolling hash compares windows in O(1), and a cheap verification step restores correctness whenever hashes happen to collide. It's the gateway to every rolling-hash / string-fingerprint technique.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternRolling Hash / String Fingerprinting. Recognise it whenever you slide a fixed-width window and need fast equality: substring search, duplicate-substring detection, and longest-duplicate-via-binary-search all ride on the O(1) rolling update plus verification.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Repeated DNA SequencesComing soonRoll a fixed-length (10) hash and bucket windows to find sequences appearing more than once.
- Longest Duplicate SubstringComing soonBinary search the length, use rolling hashes (double-hashed) to test for a repeated substring of that length.
- Find All Anagram HashesComing soonUse a frequency-based rolling signature instead of a positional polynomial hash.
- KMP / Z (guaranteed worst case)When you must avoid the O(n·m) collision worst case entirely, switch to a comparison-based linear matcher.