KMP pattern search
Find every place a pattern appears in a text without ever moving backward in the text, using a table of the pattern's own repeats.
O(n + m)Space O(m)6 practice problemsStep-through pictureThe idea
The simple way to find a pattern in a text is to try every start position and compare letter by letter. When a comparison fails, you move the start by one and compare again from the pattern's first letter. On text like aaaaaaab with the pattern aaab, that redoes the same comparisons again and again.
KMP, named after Knuth, Morris, and Pratt, never re-reads a text character. When a mismatch happens after some letters matched, it already knows those letters: they are the start of the pattern. So it asks: how much of the pattern's start is also at the end of what just matched? It can keep that much and continue.
That answer is stored in a table called lps, short for longest proper prefix that is also a suffix. lps[i] says: for the first i + 1 letters of the pattern, how long is the longest start that also appears at the end (not counting the whole thing)? With it, the search runs in O(n + m) time for a text of length n and a pattern of length m.
An everyday example
Following a dance routine
You know a routine: step, clap, step, clap, spin. You watch a dancer and count how far into the routine they are. They do step, clap, step, clap, and then a step instead of a spin. You don't start counting from zero. The last two moves were step, clap, which is how the routine begins, so you are already two moves in, and the new step might continue it. A small cheat sheet tells you, for each point in the routine, how far back to fall when the next move is wrong.
| In the example | In the code |
|---|---|
| The routine you are looking for | pattern |
| The moves the dancer makes | text, read once from left to right with i |
| How many moves of the routine you've matched | j |
| The cheat sheet of where to fall back to | The lps table |
| Falling back after a wrong move | j = lps[j - 1] |
From the slow way to kmp pattern search
- The simple search tries each start in the text and compares the pattern letter by letter. When a start fails after matching many letters, the next start re-reads most of those letters. With text and pattern full of near-matches, that is
O(n · m)comparisons. - But when a mismatch happens after
jletters matched, thosejtext letters aren't unknown: they equal the firstjletters of the pattern. So which later starts could still work is a question about the pattern alone, and it can be answered before the search begins. - A later start can only work if the pattern's beginning lines up with the end of those
jletters. The longest such overlap is the longest proper prefix ofpattern[0..j-1]that is also its suffix, where proper means shorter than the whole thing. Store it aslps[j - 1]. On a mismatch, keep that many letters matched and try the same text letter again, without moving back in the text. - Building
lpsis the same search, run on the pattern against itself, withlenplaying the part ofj. Then the search walks the text once withi, matching, falling back throughlps, and recording a match wheneverjreaches the pattern's length. That loop is KMP.
How it works
- Build
lpsfor the pattern.lenis the length of the current start that matches; it starts at0, andlps[0]is0. - For each
ifrom1, whilelen > 0andpattern[i]doesn't continue that start, fall back withlen = lps[len - 1]. If it does continue, add one tolen. Storelps[i] = len. - Search the text with
j, the number of pattern letters matched so far, starting at0. - For each text index
i, whilej > 0andtext[i]doesn't matchpattern[j], fall back withj = lps[j - 1]. - If
text[i]matchespattern[j], add one toj. - If
jreaches the pattern's length, a match ends ati, so it starts ati - j + 1. Record it and fall back withj = lps[j - 1]to look for overlapping matches.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function kmpSearch(text, pattern) {const lps = new Array(pattern.length).fill(0);let len = 0;for (let i = 1; i < pattern.length; i++) {while (len > 0 && pattern[i] !== pattern[len]) {len = lps[len - 1];}if (pattern[i] === pattern[len]) len++;lps[i] = len;}const matches = [];let j = 0;for (let i = 0; i < text.length; i++) {while (j > 0 && text[i] !== pattern[j]) {j = lps[j - 1];}if (text[i] === pattern[j]) j++;if (j === pattern.length) {matches.push(i - j + 1);j = lps[j - 1];}}return matches;}
How fast it is
| Time | O(n + m) |
|---|---|
| Extra memory | O(m) |
The text index only moves forward and every fall back undoes an earlier step forward, so the search is O(n). Building the table is O(m) for the same reason. Only the lps table grows with input size.
- Best case
O(n + m)The pattern's first letter never appears in the text:
jstays0, and each text letter is checked once after theO(m)table. - Average case
O(n + m)Any text:
ionly moves forward, and every fall back undoes an earlier step forward. - Worst case
O(n + m)Text full of near-matches, like the pattern
aabinaaaaaaaa: many fall backs, but never more than the steps forward, so about2ncomparisons at most.
Common mistakes
- Moving the text index backward after a mismatch, like the simple search does. That throws away KMP's whole advantage.
- Using
ifinstead ofwhilefor the fall back. One fall back might not be enough; keep falling back until the letter fits orjis0. - Setting
j = 0after a full match. That misses overlapping matches, such asaaappearing at0,1, and2inaaaa. Fall back tolps[j - 1]instead. - Calling it with an empty pattern.
j === pattern.lengthis true right away andlps[-1]isundefined. Decide what an empty pattern means and handle it before the loop.
When to use it
Look for these clues in a problem:
- You need every position where a pattern appears in a text, including overlapping ones.
- The text or pattern is long (say
10^5characters), so trying every start is too slow. - The question is about a string's repeats, like its shortest repeating unit or the longest start that is also an end.
- You must read the text once, as a stream, without going back.
Check yourself
Answer each one in your head first, then open it.
What is lps for the pattern aabaab?
[0, 1, 0, 1, 2, 3]. For example, lps[5] = 3, because aab both starts and ends aabaab.
Why must the prefix in lps be proper, shorter than the whole piece?
If lps[j - 1] could equal j, a fall back would leave j unchanged, and the while loop would never end.
After a full match, why does the code set j = lps[j - 1] and not 0?
The next match may overlap this one. Keeping the longest overlap finds aa at 0, 1, and 2 in aaaa; j = 0 misses 1.
Why does the text index i never move back?
On a mismatch, j falls back instead. lps already says which of the recent letters still match, so there's nothing to re-read.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 6 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
The search keeps one rule at the top of each step: j is the length of the longest start of the pattern that ends right before text[i], and it is shorter than the whole pattern. At the start, j = 0 and nothing has been read, so the rule holds.
When text[i] doesn't continue the match, the candidates for the new longest match are the starts of the pattern that are also ends of pattern[0..j-1]. lps[j - 1] is the longest of them, lps[lps[j - 1] - 1] the next longest, and so on. The while loop tries them from longest to shortest, so the first one that text[i] continues is the correct new j. No possible match is skipped.
After a full match, j = lps[j - 1] restores the rule, because the next match may overlap this one by exactly that much. Every fall back makes j smaller, and j grows by at most one per text letter, so all fall backs together are at most n. The search ends after n steps with every match recorded. Building lps follows the same argument on the pattern, in O(m).
Going deeper
3 more ideas
Why falling back to lps[j - 1] never skips a match
When j letters have matched, the last j text letters equal pattern[0..j-1]. Any match that starts later and overlaps them must begin with a start of the pattern that is also an end of those j letters. The longest such overlap is lps[j - 1], so jumping there is the biggest jump that can't skip a possible match. Smaller overlaps are tried next if this one fails too.
Why it is linear
In the search, i only moves forward, n times. j grows by at most one per step and every fall back makes it smaller, so the total number of fall backs can't be more than the total growth, n. That makes the search O(n). The same argument makes building lps O(m). The table uses O(m) memory.
Uses of the table on its own
lps[m - 1] is the longest start of the pattern that is also its end. If m is divisible by m - lps[m - 1], the pattern is a smaller unit repeated, and that unit has length m - lps[m - 1]. For example abcabcabc has lps[8] = 6, so it is abc repeated three times.
Work it by hand
Every step in a table, with why
Find every start of rara in a 9-letter text. One mismatch needs two fall backs, and the two matches overlap.
text = "rarrarara", pattern = "rara"
| Step | Phase | i | Letter | Fall back | len or j | What happens | Why |
|---|---|---|---|---|---|---|---|
| 1 | build | 1 | a | - | len = 0 | a !== r: lps[1] = 0 | ra has no start that is also an end. |
| 2 | build | 2 | r | - | len = 1 | r === r: lps[2] = 1 | rar starts and ends with r. |
| 3 | build | 3 | a | - | len = 2 | a === a: lps[3] = 2 | rara starts and ends with ra. The table is [0, 0, 1, 2]. |
| 4 | search | 0 | r | - | j = 1 | match | The search starts with j = 0. |
| 5 | search | 1 | a | - | j = 2 | match | ra matched so far. |
| 6 | search | 2 | r | - | j = 3 | match | rar matched; the pattern wants a next. |
| 7 | search | 3 | r | 3 → 1 → 0 | j = 1 | fall back twice, then match | r isn't pattern[3]. Fall back to j = lps[2] = 1, but pattern[1] is a, still wrong. Fall back to j = lps[0] = 0; pattern[0] is r, which matches. i never moves back. |
| 8 | search | 4 | a | - | j = 2 | match | A new attempt that began at index 3. |
| 9 | search | 5 | r | - | j = 3 | match | rar again. |
| 10 | search | 6 | a | - | j = 4 → 2 | full match at 3 | j reaches 4, so a match starts at 6 - 4 + 1 = 3. Then j = lps[3] = 2 keeps ra, which may begin the next match. |
| 11 | search | 7 | r | - | j = 3 | match | The kept ra plus this r. |
| 12 | search | 8 | a | - | j = 4 → 2 | full match at 5 | The second match starts at 5, overlapping the first. The text has ended. |
It returns [3, 5]. The text index i moved forward 9 times and never back; the only extra work was the two fall backs at i = 3.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| KMP pattern search | O(n + m) | O(m) | One pattern, with a guaranteed linear time whatever the text looks like. |
| Simple search | O(n · m) | O(1) | Short patterns in ordinary text. In practice, use the built-in indexOf or includes, which are heavily tuned. |
| Rabin-Karp rolling hash | O(n + m) | O(1) | Many patterns of the same length, or quick checks of whether two pieces are equal. Its worst case is slower. |
| Z-algorithm | O(n + m) | O(n + m) | The same result from a different idea that some people find easier to remember. |
Extra memory: O(m) for lps, plus the matches list. The text is read one letter at a time, so it could even come from a stream.
How the steps grow
- KMP pattern search, Rabin-Karp rolling hash, Z-algorithm
O(n + m) - Simple search
O(n · m)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| KMP pattern search | 20 | 2,000 | 2 million |
| Simple search | 100 | 1 million | 1 trillion |
| Rabin-Karp rolling hash | 20 | 2,000 | 2 million |
| Z-algorithm | 20 | 2,000 | 2 million |
Inputs that need care
5 tricky inputs
- An empty pattern:
kmpSearch("abc", "") j === pattern.lengthis true at once, so it pushes a match at1, then setsjtolps[-1], which isundefined. It returns[1], which makes no sense. Return early with whatever an empty pattern should mean.- A pattern longer than the text:
kmpSearch("ab", "abc") jcan never reach 3, so it returns[]after reading the text once. Comparing lengths first skips building the table.- Overlapping matches:
kmpSearch("aaaa", "aa") - Falling back to
lps[1] = 1after each match finds the starts0,1, and2. Settingj = 0instead finds only0and2. - A pattern with no repeats:
"abcd" lpsis all zeros, so every mismatch falls back toj = 0. KMP then acts like the simple search, but still never movesiback.- Accented letters written two ways
écan be one character orefollowed by an accent mark, two characters. Then the same-looking text doesn't match. Callnormalize("NFC")on both strings first.
Variations and follow-ups
Twists interviewers add
One loop over a joined string
Build lps for pattern + "#" + text, where # appears in neither. Every position whose value equals pattern.length ends a match. It is one loop instead of two, with the same O(n + m) time but more memory.
Shortest palindrome by adding in front
Build lps for text + "#" + reversed text. The last value is the length of the longest palindrome that starts at index 0. Putting the rest of the text, reversed, in front gives the shortest palindrome that ends with text.
A table of next states
For a small alphabet, compute ahead of time, for each j and each letter, what j becomes. The search then does one table lookup per letter with no fall back loop, using O(m · alphabet) memory.
Many patterns at once
To find many patterns in one pass, Aho-Corasick puts all the patterns in a trie and adds fall back links between its nodes. Those links are the same idea as lps, spread across all the patterns.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "find all occurrences" with a time guarantee, "the shortest palindrome by adding letters in front", or "is this string a smaller piece repeated". The
lpstable alone answers the last two. - Explain
lpswith an example before coding: "forrara,lps[3] = 2, becauseraboth starts and ends it, so after a mismatch I keep those 2 letters". - Point out that both loops are the same idea: building the table matches the pattern against itself. Writing them the same way avoids bugs.
- Test by hand: overlapping matches like
aainaaaa, a pattern with no repeats, and a fall back that needs two steps. The usual follow-up is the running time:jcan fall back at most as many times as it grew.
Where frontend code uses it
The same idea in real UI code
- Find-in-page and editor search boxes find every occurrence of a query in a long document; a linear-time search like KMP keeps that fast on big files.
- Highlighting all matches of a search term in a chat log or table cell is the same "every position, overlaps allowed" task.
- Streaming parsers that look for a marker, such as a multipart boundary in an uploaded body or the end of an event in a streamed response, can use KMP because it never needs to look back at earlier chunks.