KMP Pattern Matching

Strings & Backtracking
avg O(n + m)

KMP (Knuth-Morris-Pratt) finds a pattern inside a text without ever re-reading a text character. Naive matching backs up and restarts after every mismatch - on repetitive input that costs O(n·m). KMP first builds a small table from the pattern alone: for every position, how much of the pattern is automatically still matched after a failure. The memorable part: all the speed comes from what the pattern knows about itself - the text is read once, forward, in O(n + m) total.

Watch the pattern slide

Press play - or drag the timeline and step through it yourself.
Comparison 1Comparisons 0Step 1 / 26
012345678910
text···········
patternABABC······
lps···········

Goal: find ABABC inside a text. But first, the pattern studies itself: for each position, the lps row will store the length of the longest prefix that is also a suffix of the part read so far.

being compared compared against the match mismatch

Complexity

Best
O(n + m)
Input already in order
Average
O(n + m)
Normal mixed input
Worst
O(n + m)
Worst possible input
Space
O(m)
Extra memory used

A conveyor belt that never rewinds

Letters pass by on a conveyor belt, one per second, and you are watching for the word ABABC. The belt never rewinds - once a letter is gone, it is gone. You have matched A-B-A-B, and the next letter is A, not the C you need. A beginner panics and restarts from nothing. But notice: the last two letters you just saw were A-B, and your word also starts with A-B - so you are already two letters into a fresh attempt. KMP writes that trick down for every position, before the belt even starts moving.

How it works, step by step

  1. Before touching the text, study the pattern. Build the lps table: for each position, the length of the longest proper prefix of the pattern that is also a suffix of the part read so far.

  2. That table answers one question: "I matched j characters and then failed - how many of them still count after sliding the pattern forward?"

  3. Now scan. Text pointer i, pattern pointer j. When text[i] equals pattern[j], both move forward - this part is exactly like naive matching.

  4. On a mismatch with j > 0, do not move i. Set j = lps[j - 1]: the pattern slides forward, the overlap is kept, and no text is re-read.

  5. On a mismatch with j = 0, there is nothing to reuse - move i forward by one.

  6. When j reaches the pattern length, a match starts at i - j. Set j = lps[j - 1] and keep scanning to find every occurrence, overlapping ones included. Total work: O(n + m).

The code, in JavaScript

The heart of KMP. Built from the pattern ONLY - it never sees the text. lps[i] answers: how long is the longest proper prefix of the pattern that is also a suffix ending at i?

javascript
function buildLps(pattern) {
  // lps[i] = length of the longest PROPER prefix of pattern.slice(0, i + 1)
  // that is also a suffix of it. "Proper" = shorter than the piece itself,
  // which is why lps[0] is always 0.
  const lps = new Array(pattern.length).fill(0);
  let len = 0;             // length of the current border (prefix = suffix)
  let i = 1;               // lps[0] is settled, start at 1

  while (i < pattern.length) {
    if (pattern[i] === pattern[len]) {
      // The border grows by one character.
      len++;
      lps[i] = len;
      i++;
    } else if (len > 0) {
      // The border cannot grow. Do NOT reset to zero:
      // a shorter border may still work - and lps already knows it.
      len = lps[len - 1];  // note: len - 1, not len
    } else {
      // No border at all ends here.
      lps[i] = 0;
      i++;
    }
  }
  return lps;
}

buildLps("ABABC");   // → [0, 0, 1, 2, 0]
buildLps("AAAA");    // → [0, 1, 2, 3]
buildLps("ABCDE");   // → [0, 0, 0, 0, 0]

Dry run: finding ABABC inside ABABABCABAB

The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.

StepComparisonWhat happened
11lps[0] = 0, always. The prefix must be "proper" - shorter than the piece itself - and one letter has nothing shorter.
21Position 1: B is not A. No prefix of "AB" is also a suffix - lps[1] = 0.
32Position 2: "ABA" ends with "A" - exactly how it starts. lps[2] = 1.
43Position 3: "ABAB" ends with "AB" - exactly how it starts. lps[3] = 2.
54Position 4: C is not A, so the "AB" border cannot grow. Try the shorter border lps[1] = 0 - do not restart from zero.
65Position 4: C is not A. No prefix of "ABABC" is also a suffix - lps[4] = 0.
76A = A - match, both pointers advance.
87B = B - match, both pointers advance.
98A = A - match, both pointers advance.
109B = B - match, both pointers advance.
1110Mismatch at text position 4: the text has A, the pattern wants C.
1210But "ABAB" ends with "AB", which is also how the pattern starts. Slide the pattern so those overlap - the text pointer NEVER goes back.
1311Text position 4 again: A = A - the comparison resumes mid-pattern. Nothing was re-read.
1412B = B - match, both pointers advance.
1513C = C - the whole pattern has matched!
1613Match found at position 2: text[2..6] = ABABC.
1714A = A - match, both pointers advance.
1815B = B - match, both pointers advance.
1916A = A - match, both pointers advance.
2017B = B - match, both pointers advance.

Good choice when…

  • The text is huge or arrives as a stream - the text pointer never backs up, so you can scan a file or network packets byte by byte with no rewind buffer.
  • The input is repetitive (DNA, log files full of AAAA...) - exactly where naive matching degrades to O(n·m), KMP stays O(n + m).
  • You need all occurrences, including overlapping ones, in guaranteed linear time.
  • You search the same pattern in many texts - build the lps table once, reuse it everywhere.

Bad choice when…

  • A one-off search in a normal-sized string - `text.indexOf(pattern)` is one call, runs native code, and is very hard to beat.
  • Many patterns at once (a dictionary of banned words) - that is Aho-Corasick territory: a trie of all the patterns sharing one scan of the text.
  • Approximate matching ("one typo allowed") - KMP is strictly exact. Edit-distance DP is the tool there.

Common mistakes

  • The lps table is built from the pattern only. A surprisingly common bug is trying to build it from the text. The table is what the pattern knows about itself - the text is only ever read once, forward.
  • "Proper" prefix means: not the whole string. That is why lps[0] is always 0. Allow the whole string and every lps[i] becomes i + 1 - the pattern never really slides, and the search can loop forever.
  • On a mismatch you consult `lps[j - 1]`, not `lps[j]`. You are asking about the j characters that DID match, not the one that failed. This off-by-one often survives easy tests and then breaks on repetitive input.
  • For one small search, KMP is often *slower* than naive - the table costs time and the constant factor is higher. It pays off on repeated searches, streams, and adversarial input. Know why you are reaching for it.

KMP Pattern Matching vs. its closest relatives

AlgorithmBestAverageWorstSpaceStable
KMP Pattern Matchingthis pageO(n + m)O(n + m)O(n + m)O(m)-
Trie (Prefix Tree)O(m)O(m)O(m)O(n·m)-
Longest Common SubsequenceO(n·m)O(n·m)O(n·m)O(n·m)-
Sliding WindowO(n)O(n)O(n)O(k)-