Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path →
KMP Failure Table
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.
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.
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.
Examples and constraints 5 examples
pattern = 'ababab'[0, 0, 1, 2, 3, 4]pattern = 'aabaa'[0, 1, 0, 1, 2]pattern = 'aabaaab'[0, 1, 0, 1, 2, 2, 3]pattern = 'a'[0]pattern = 'aaab'[0, 1, 2, 0]- 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
lps[0] = 0always. Maintaink= length of the longest prefix that is also a suffix ending just beforei.- At position
i: whilek > 0andpattern[i] != pattern[k], setk = lps[k-1]— reuse the table to fall back. - If
pattern[i] == pattern[k], incrementk. Then setlps[i] = k. - 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].
| step | char | k before | comparisons | lps[i] = k after |
|---|---|---|---|---|
| i = 0 | a | — | none: lps[0] is always 0 | 0 |
| i = 1 | a | 0 | a = pattern[0]: extend | 1 |
| i = 2 | b | 1 | b ≠ pattern[1] (a): k = lps[0] = 0; b ≠ pattern[0] | 0 |
| i = 3 | a | 0 | a = pattern[0]: extend | 1 |
| i = 4 | a | 1 | a = pattern[1]: extend | 2 |
| i = 5 | a | 2 | a ≠ pattern[2] (b): k = lps[1] = 1; a = pattern[1]: extend | 2 |
| i = 6 | b | 2 | b = pattern[2]: extend | 3 |
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]: thebfalls back twice, 2 → 1 → 0. Writing the fallback as a singleifstops 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
ifinstead ofwhilefor 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 > 0and the text character differs frompattern[j], setj = lps[j - 1]; on equality,j += 1. When j reaches m, record a match starting ati - m + 1and setj = 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 whenlps[m - 1] > 0and 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.
Explore the topic
See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.
More Challenges
- Two SumThe classic warm-up: find the two numbers that add up to a target. Brute force is O(n²) — a hash map gets you to one pass, O(n). Solve it in Python or TypeScript, right in your browser, with hidden tests and a reveal-solution button.Read →
- Valid ParenthesesThe canonical stack problem: decide whether every bracket is closed by the right type, in the right order. A stack turns nested matching into a single pass. Solve it in Python or TypeScript with hidden tests.Read →
- Fizz BuzzThe famous screening question. Print 1…n, but multiples of 3 become "Fizz", of 5 become "Buzz", and of both become "FizzBuzz". Easy — the catch is testing divisibility in the right order. Solve it in Python or TypeScript.Read →
- Number of IslandsThe canonical connected-components question. Flood-fill each unvisited patch of land with BFS or DFS, count how many floods you started — grid traversal, visited bookkeeping, and the classic diagonal trap. Solve it in Python or TypeScript.Read →
- Merge IntervalsCalendar apps and memory allocators run on this: sort by start, sweep once, grow or close the current block. Touching endpoints merge, nested intervals vanish — the classic that punishes off-by-one thinking. Solve it in Python or TypeScript.Read →
- Top-K Frequent ElementsCount, then rank — the two-step pattern behind trending topics and hot-key detection. A hash map counts; a (-count, value) sort ranks with a clean tie-break; buckets get you to O(n) if you want the flex. Solve it in Python or TypeScript.Read →