Never re-read matched text: a prefix (failure) table says how far to jump on a mismatch — O(n+m).
KMP (Knuth-Morris-Pratt) fixes the naïve method's core waste: after a partial match fails, the naïve search throws away everything it learned and re-reads the text. KMP refuses to. Before searching, it builds a small prefix table (the *lps* array, also called the failure function) straight from the pattern, and uses it to jump the pattern forward on a mismatch *without* moving the text pointer backward. The text is scanned exactly once.
For each pattern position, lps[i] is the length of the longest proper prefix of the pattern (ending at i) that is also a suffix ending there. A *proper* prefix excludes the whole string itself. Intuitively: when a match breaks at position i, the matched part pattern[0..i-1] is known, and lps[i-1] tells you the longest already-matched chunk you can keep instead of restarting at zero.
Walk the pattern, tracking the current matched prefix length len. When pattern[i] extends the prefix, len grows; when it breaks, fall back to lps[len-1] and try again.
index : 0 1 2 3 4 5
char : A B A B A C
lps : 0 0 1 2 3 0
lps[2]=1: prefix "A" reappears as a suffix of "ABA"
lps[4]=3: prefix "ABA" reappears as a suffix of "ABABA"
lps[5]=0: nothing of "ABABAC" ending in C starts the patternfunction buildLps(p) {
const lps = new Array(p.length).fill(0);
let len = 0;
for (let i = 1; i < p.length; ) {
if (p[i] === p[len]) lps[i++] = ++len; // extend the prefix
else if (len > 0) len = lps[len - 1]; // fall back, retry
else lps[i++] = 0; // no prefix here
}
return lps;
}i; track how much of the pattern matches with j.text[i] === pattern[j], advance both i and j.j reaches the pattern length → a hit; set j = lps[j-1] to keep scanning for more.j > 0, set j = lps[j-1] — jump the pattern forward, leave `i` put.j == 0, just advance i.lps encodes everything the matched prefix already told us, so a mismatch can be resolved by repositioning the *pattern* alone. Because i only ever moves forward, the text is read once — that is the entire reason KMP is linear.lps[len-1], not simply len-1. Decrementing by one would forget the prefix structure and quietly break the algorithm.