Learn/DSA Patterns
DSA PatternsStringshard8 min read

KMP Algorithm (Pattern Matching)

KMP finds all occurrences of a pattern inside a text in O(n + m) time by never going backwards in the text. The secret is the failure function — a pre-computed array that tells the matcher where to resume in the pattern after a mismatch, using the pattern's own overlap structure.

#kmp#pattern-matching#failure-function#strings#prefix#suffix#beginner#interview
Table of contents

The problem with brute force

You want to find all places where pattern p (length m) appears inside text t (length n). Brute force: slide p over every position in t and compare. On a mismatch, shift by 1 and restart — O(n × m).

For n = 1 000 000 and m = 1 000, that is 1 billion character comparisons. Knuth, Morris, and Pratt found a way to do it in O(n + m) — by never going backwards in the text.


The reading comprehension story

A real-life story

You're reading a text looking for the word "ABCABD". Your eye scans forward — A, B, C, A, B — then hits a mismatch. Naive approach: go back to where you started and try again from position 2.

But a smarter reader thinks: "I just saw 'ABCAB' which ends with 'AB' — that same 'AB' is also the start of my pattern. So I don't have to restart from A; I can resume from position 2 in the pattern (after 'AB') and keep my eye where it is in the text."

The KMP failure function is that smarter reader's memory — it records, for every position in the pattern, "if I mismatch here, where can I safely resume in the pattern?"


Step 1 — Build the failure function

The failure function fail[i] stores the length of the longest proper prefix of p[0..i] that is also a suffix of p[0..i].

"Proper" means not the full string itself.

Pattern: "ABCABD"

i=0  p[0..0]="A"      prefixes: ""         → fail[0] = 0
i=1  p[0..1]="AB"     longest match: ""    → fail[1] = 0
i=2  p[0..2]="ABC"    longest match: ""    → fail[2] = 0
i=3  p[0..3]="ABCA"   longest match: "A"   → fail[3] = 1
i=4  p[0..4]="ABCAB"  longest match: "AB"  → fail[4] = 2
i=5  p[0..5]="ABCABD" longest match: ""    → fail[5] = 0

fail = [0, 0, 0, 1, 2, 0]

How to build it in O(m):

def build_failure(pattern: str) -> list[int]:
    m    = len(pattern)
    fail = [0] * m
    j    = 0               # length of current matching prefix

    for i in range(1, m):
        while j > 0 and pattern[i] != pattern[j]:
            j = fail[j - 1]    # fall back using previously computed values
        if pattern[i] == pattern[j]:
            j += 1
        fail[i] = j

    return fail

Pause & think

Why does the inner while use j = fail[j - 1] instead of just j -= 1? What does falling back to fail[j-1] achieve?

Answer

If pattern[i] != pattern[j], j must retreat. But retreating by 1 (j -= 1) might still be wrong — pattern[i] might not match pattern[j-1] either. fail[j-1] tells us the longest proper prefix-suffix for p[0..j-1], which is the best shorter prefix we can try. We keep falling back this way (using earlier fail values) until either we find a match or j reaches 0. This is what makes the total work O(m) — each character is added to and removed from j at most once.


Step 2 — Search the text

Using fail, search for all occurrences of pattern in text:

def kmp_search(text: str, pattern: str) -> list[int]:
    n, m = len(text), len(pattern)
    if m == 0:
        return []

    fail    = build_failure(pattern)
    matches = []
    j       = 0               # number of pattern characters matched so far

    for i in range(n):
        while j > 0 and text[i] != pattern[j]:
            j = fail[j - 1]   # mismatch: fall back in pattern, NOT in text

        if text[i] == pattern[j]:
            j += 1

        if j == m:            # full pattern matched
            matches.append(i - m + 1)
            j = fail[j - 1]   # prepare for next possible match

    return matches

The key invariant: i (the text pointer) never goes backward. Only j (the pattern pointer) falls back, and it falls back using precomputed fail values — O(1) per fall-back amortised.


Frame-by-frame: text = "ABCABCABD", pattern = "ABCABD"

fail = [0, 0, 0, 1, 2, 0]   (computed in Step 1)

i=0  t[0]='A', j=0: match → j=1
i=1  t[1]='B', j=1: match → j=2
i=2  t[2]='C', j=2: match → j=3
i=3  t[3]='A', j=3: match → j=4
i=4  t[4]='B', j=4: match → j=5
i=5  t[5]='C', j=5: mismatch (pattern[5]='D')
                  → j = fail[4] = 2
     t[5]='C', j=2: match → j=3
i=6  t[6]='A', j=3: match → j=4
i=7  t[7]='B', j=4: match → j=5
i=8  t[8]='D', j=5: match → j=6 == m → MATCH at position 8-6+1 = 3j = fail[5] = 0

Matches: [3]  ✅  (text[3..8] = "ABCABD")

Notice: at i=5, the mismatch sent j from 5 to 2 — we kept "ABC" progress from the previous window. The text pointer i never moved back.


Application: Repeated Substring Pattern (LC #459)

Does string s consist of a repeated substring? E.g. "abcabc" = "abc" repeated twice.

KMP trick: concatenate s + s, remove first and last character, check if s is a substring.

def repeatedSubstringPattern(s: str) -> bool:
    doubled = (s + s)[1:-1]    # remove first and last char
    return s in doubled        # Python's `in` uses KMP internally

Why does this work? If s has a repeating period p, then s + s contains a copy of s starting at position p (not 0 and not len(s)). Removing the first and last characters prevents the trivial match at position 0 and at the end.


The one idea to remember

The entire pattern in one sentence

KMP pre-computes the failure function — the longest proper prefix-suffix at each pattern position — so that on a mismatch, the pattern pointer falls back to the right resume point without ever retreating the text pointer, achieving O(n + m) total work.


Where to spot this pattern

Trigger words:

  • "find all occurrences of pattern in text"
  • "implement strStr()" or "needle in haystack"
  • "repeated substring" — does it consist of a smaller repeated block?
  • "shortest repeating period"

Distinguish from Rolling Hash (2.05): Both solve pattern matching in O(n). KMP has zero false positives (deterministic), Rolling Hash may need verification. For interviews, either is acceptable — KMP is more common for classic pattern matching; Rolling Hash is favoured for "find repeated/distinct substrings" problems.


Common traps

Watch out for these

  • Building fail from index 0 instead of 1. fail[0] is always 0 by definition (no proper prefix for a single character). The build loop starts at i = 1.
  • Off-by-one in match reporting. When j == m, the match starts at i - m + 1, not i - m.
  • Forgetting to reset j after a full match. After finding a match, set j = fail[j - 1] to prepare for overlapping matches. Resetting to 0 would miss overlapping occurrences.
  • Confusing fail[j-1] with fail[j]. During search, when mismatching at pattern[j], fall back to fail[j-1] (the fail value for the last matched position), not fail[j].

Complexity

PhaseTimeSpace
Build failure functionO(m)O(m)
Search textO(n)O(1) extra
TotalO(n + m)O(m)

Remember this forever

KMP — Two phases

# Phase 1: Build failure function — O(m)
fail = [0] * m;  j = 0
for i in range(1, m):
    while j > 0 and p[i] != p[j]: j = fail[j-1]
    if p[i] == p[j]: j += 1
    fail[i] = j

# Phase 2: Search — O(n)
j = 0
for i in range(n):
    while j > 0 and t[i] != p[j]: j = fail[j-1]
    if t[i] == p[j]: j += 1
    if j == m: record i-m+1; j = fail[j-1]

Text pointer never goes backward. Only j falls back.

Trap: after match, j = fail[j-1] not 0 (for overlapping matches).


Check yourself

What does `fail[i] = k` mean in plain English?

The pattern's first k characters (p[0..k-1]) are identical to the last k characters of p[0..i]. It is the length of the longest proper prefix of p[0..i] that is also a suffix. On a mismatch at position i+1, we can resume matching from position k in the pattern — we already know k characters match without checking them again.

Why is the total number of `j = fail[j-1]` operations across the entire search O(n)?

j can only increase by 1 per text character (via j += 1). Over n characters, j increases at most n times total. Each fall-back (j = fail[j-1]) strictly decreases j. So the total number of decreases is bounded by the total number of increases: O(n). This amortised argument proves the search phase is O(n).


Practice problems

ProblemDifficultyWhat to noticeLink
Implement strStr()EasyDirect KMP applicationLC #28
Repeated Substring PatternEasyKMP trick: s in (s+s)[1:-1]LC #459
Shortest PalindromeHardReverse + KMP: find longest palindrome prefixLC #214
KMP Count (find all occurrences)MediumLoop after match: j = fail[j-1], not 0Custom practice

Next up: Z-Algorithm — a simpler alternative to KMP that computes the Z-array: the length of the longest substring starting at each position that matches a prefix of the string.