Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

CODING CHALLENGE · N°36

KMP Failure Table

Medium StringsPattern MatchingDynamic Programming

The precomputation that makes Knuth–Morris–Pratt string search run in O(n): for every prefix of the pattern, the length of its longest proper prefix that is also a suffix. Get this table right and matching never backtracks. Build it in Python or TypeScript, with hidden tests.

The problem

Given a non-empty string pattern, return its failure table (a.k.a. the longest-proper-prefix-suffix / LPS array). Entry lps[i] is the length of the longest proper prefix of pattern[0..i] that is also a suffix of it. ("Proper" means not the whole substring.) This is the table KMP uses to skip re-comparisons after a mismatch.

SOLVE IT YOURSELF

Your turn — write it

Edit the stub, hit Run (or ⌘/Ctrl + Enter), and watch the hidden tests. Stuck? open the hints under the problem, or Reveal solution is one click away.

YOUR TASK

Implement failure_table(pattern): keep a length k of the current longest prefix-suffix. Walk i from 1; on mismatch fall back to lps[k-1]; on match extend k; record lps[i] = k.

CPython · WebAssembly
Examples and constraints 5 examples
EXAMPLE 1
Input pattern = 'ababab'
Output [0, 0, 1, 2, 3, 4]
"abab" has prefix "ab" = suffix "ab" → 2, and so on
EXAMPLE 2
Input pattern = 'aabaa'
Output [0, 1, 0, 1, 2]
the trailing "aa" matches the leading "aa" → 2
EXAMPLE 3
Input pattern = 'aabaaab'
Output [0, 1, 0, 1, 2, 2, 3]
at index 5 the border "aa" fails and the table falls back to "a", not to 0
EXAMPLE 4
Input pattern = 'a'
Output [0]
edge case: a single character has no proper prefix
EXAMPLE 5
Input pattern = 'aaab'
Output [0, 1, 2, 0]
edge case: the b falls back twice, 2 → 1 → 0
CONSTRAINTS
  • 1 ≤ pattern.length; the first entry is always 0 (a single char has no proper prefix).
  • Build it in O(n) with the two-pointer method — do not recompute prefixes from scratch (O(n²)).
  • On a mismatch while extending, fall back via lps[k-1] rather than restarting at 0.
Hints 4 ideas
HINTS — 4 IDEAS
  1. lps[0] = 0 always. Maintain k = length of the longest prefix that is also a suffix ending just before i.
  2. At position i: while k > 0 and pattern[i] != pattern[k], set k = lps[k-1] — reuse the table to fall back.
  3. If pattern[i] == pattern[k], increment k. Then set lps[i] = k.
  4. Each character advances or falls back k, so the whole thing is amortized O(n).
Approach, complexity & discussion — open after you solve

Start from brute force

The obvious approach computes each entry from its definition: for every i, try lengths L from i down to 1 and stop at the first L where pattern[:L] == pattern[i-L+1:i+1]. It works because, counting down, the first length that matches is the longest proper border. It costs O(m³) time in the worst case (up to m lengths at each of m positions, each check up to m characters) and O(m) space for the slices. It is too slow because position i learns nothing from position i − 1: on "a" * 500 + "b" + "a" * 500 (1,001 characters) it makes about 63 million character comparisons, against about 2,500 for the linear build.

The approach

The insight is that borders nest. If pattern[0..i] has a border of length L, dropping its last character leaves a border of length L − 1 of pattern[0..i-1]. So the candidates for lps[i] are the borders of the previous prefix, each extended by one character, and those borders are already in the table: the longest is k, the next is lps[k-1], then lps[lps[k-1]-1], and so on. The invariant: at the top of iteration i, k = lps[i-1].

The reference starts with lps = [0] * n and k = 0 and walks i from 1. While k > 0 and pattern[i] != pattern[k], it falls back with k = lps[k - 1], trying the next-shorter border. If pattern[i] == pattern[k], the border extends and k += 1. Either way it records lps[i] = k. It only reaches zero when the whole chain of borders is used up.

Walk through an example

Trace on "aabaaab", the third example, because index 5 shows a fallback that lands on a shorter border instead of zero; the last column read top to bottom is the answer [0, 1, 0, 1, 2, 2, 3].

stepchark beforecomparisonslps[i] = k after
i = 0a—none: lps[0] is always 00
i = 1a0a = pattern[0]: extend1
i = 2b1b ≠ pattern[1] (a): k = lps[0] = 0; b ≠ pattern[0]0
i = 3a0a = pattern[0]: extend1
i = 4a1a = pattern[1]: extend2
i = 5a2a ≠ pattern[2] (b): k = lps[1] = 1; a = pattern[1]: extend2
i = 6b2b = pattern[2]: extend3

Complexity

Two-pointer build: O(m) time, because k rises by at most 1 per index and every fallback lowers it, so the while loop runs at most m − 1 times in total; O(m) space for lps and O(1) besides. Brute force: O(m³) time in the worst case, from m lengths tried at m positions with checks of up to m characters, and O(m) space.

Edge cases to test

  • A single character, "a": the loop never runs and the answer is [0].
  • No character repeats, "abcd": [0, 0, 0, 0].
  • All the same, "aaaa": [0, 1, 2, 3]; every index extends and nothing falls back.
  • "aaab" must give [0, 1, 2, 0]: the b falls back twice, 2 → 1 → 0. Writing the fallback as a single if stops at k = 1 and records [0, 1, 2, 1].
  • "aabaaa" must give [0, 1, 0, 1, 2, 2]. Resetting k to 0 on a mismatch throws away the border "a" and records 1 at the last index.

Common mistakes

  • Off-by-one in “proper prefix” — it excludes the whole string.
  • Resetting the length to 0 on a mismatch instead of falling back through the table.
  • Using if instead of while for the fallback: one step back is not always enough, and "aaab" comes out as [0, 1, 2, 1].
  • Blurring the table-build with the search loop — build first, then match.

Follow-ups an interviewer asks next

Use the table to find every occurrence of the pattern in a text.
Run the same loop over the text with j as the matched length: while j > 0 and the text character differs from pattern[j], set j = lps[j - 1]; on equality, j += 1. When j reaches m, record a match starting at i - m + 1 and set j = lps[m - 1] so overlapping matches are found. The text pointer never moves backward, so the search is O(n) after the O(m) build.
Does the string consist of a repeated substring?
Use the last entry. The shortest period is p = m - lps[m - 1], and the string is a whole number of copies of its first p characters exactly when lps[m - 1] > 0 and p divides m. For "ababab", lps[5] = 4, so p = 2 and the string is "ab" three times; for "aabaa", p = 3 does not divide 5.
Search for many patterns at once.
Running KMP once per pattern costs a full pass over the text per pattern. Aho-Corasick generalizes the failure table to a trie of all the patterns: each node gets a failure link to the longest proper suffix of its string that is also a path in the trie, and one pass over the text finds every occurrence of every pattern in O(n + total pattern length + matches).
Can you skip writing a separate search loop?
Yes: build the table of pattern + "#" + text, with a separator that occurs in neither string. Every index whose entry equals m is the end of an occurrence. The separator caps every entry at m, because a border longer than m would need a second separator in the string.

Where this shows up

The failure table is the heart of KMP string matching — it is why KMP runs in O(n + m) and its text pointer never moves backward, which also means it can match a stream without buffering or backing up. Generalized from one pattern to a trie of many, the same failure links become Aho-Corasick, the multi-pattern matcher used in keyword scanners and intrusion-detection signature matching. Border arrays like this one are also a standard tool for finding periods and repeats in strings, DNA included.

Reference solution

Reference solution — Python
def failure_table(pattern):
    n = len(pattern)
    lps = [0] * n
    k = 0                            # length of current longest prefix-suffix
    for i in range(1, n):
        while k > 0 and pattern[i] != pattern[k]:
            k = lps[k - 1]           # fall back using the table itself
        if pattern[i] == pattern[k]:
            k += 1                   # extend the matched prefix
        lps[i] = k
    return lps


print(failure_table("ababab"))   # -> [0, 0, 1, 2, 3, 4]
print(failure_table("aabaa"))    # -> [0, 1, 0, 1, 2]
Reference solution — TypeScript
function failureTable(pattern: string): number[] {
  const n = pattern.length;
  const lps = new Array(n).fill(0);
  let k = 0;                         // current longest prefix-suffix length
  for (let i = 1; i < n; i++) {
    while (k > 0 && pattern[i] !== pattern[k]) k = lps[k - 1]; // fall back
    if (pattern[i] === pattern[k]) k++;                         // extend
    lps[i] = k;
  }
  return lps;
}

console.log(failureTable("ababab")); // -> [0,0,1,2,3,4]
console.log(failureTable("aabaa"));  // -> [0,1,0,1,2]

Questions about KMP Failure Table

What does the KMP failure table, or LPS array, store?

Entry i is the length of the longest proper prefix of pattern[0..i] that is also a suffix of it, called its longest border. For abab the entry is 2, because ab is both a prefix and a suffix. During a search, when the text stops matching after j pattern characters, that entry says how much of the matched part can still be the start of an occurrence, so KMP resumes from lps[j - 1] instead of starting over and never moves backward in the text. Proper means the whole substring does not count, or every entry would equal its own length.

Why is building the failure table O(m) when it has a nested while loop?

Because the loop variable k has a budget. Each index raises k by at most one, so across the whole pattern k is raised at most m - 1 times. Every pass through the while loop lowers k by at least one, and k never goes below zero, so the loop cannot run more times in total than k was raised. That bounds all the fallbacks together at m - 1, and the outer loop runs m - 1 times, so the total work is linear even though a single index can fall back several times.

Why does KMP fall back to lps[k - 1] instead of resetting to zero?

Because a shorter border may still extend. When the character after the current border of length k does not match, the next candidate is the longest border of that border, which is lps[k - 1], and it is already in the table. Resetting to zero skips every candidate in between. For aabaaa, at the last index the border aa fails, lps[1] = 1 says the border a is still there, and extending it gives the correct 2. Resetting to zero records 1 instead.

What other problems does the LPS array solve?

Any problem about borders or periods of a string. The shortest period of a string of length m is m minus its last LPS entry, which answers whether a string is a repeated substring. Building the table of a string, a separator and the reversed string gives the longest palindromic prefix, the core of the shortest palindrome problem. Building it for the pattern, a separator and the text finds every occurrence of the pattern. Aho-Corasick extends the same failure-link idea from one pattern to a trie of many.

Finished this one? 0 / 76 Challenges done
NextLongest Common Subsequence Next on the “Graphs, strings & parsers” ladder · rung 5 of 8

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More Challenges