skip to content

When is KMP not worth using over a naive substring scan in production?

level: seniorimportance: should knowfreq 45%

answer

  1. Worst case is not typical behaviour
  2. Count what the guarantee costs you
  3. Think about the inner loop's constants
  4. Ask who chooses the input
  5. Self-similar or untrusted data flips it

basics

~20 s

On varied text with short patterns a naive scan already runs close to linear with a tighter inner loop, so KMP's table build and extra memory rarely pay off. Pay for it when inputs are repetitive or attacker-chosen.

solid answer

~50 s

The naive worst case is O(n*m), but its *expected* behaviour on varied text is close to O(n) — mismatches happen within a character or two, so the inner loop rarely runs deep. Against that, KMP charges an O(m) table build per pattern, O(m) memory, and a table lookup on the mismatch path that a straight compare-and-advance loop does not have. So on natural-language logs with short search terms, naive scanning — or a skip-based matcher, which can genuinely run sublinear — typically wins on wall clock. KMP earns its keep when the data is self-similar (long runs, periodic binary payloads), when the pattern and text are attacker-influenced and a quadratic blowup becomes a denial-of-service vector, or when you need a hard bound you can put in a latency budget. And there is a maintenance cost: an off-by-one in a hand-rolled table silently misses matches.

go deeper

for a junior

Recall that the naive scan's quadratic label is a worst case, not what usually happens, and that better asymptotics do not automatically mean a faster program.

for a middle

Explain the concrete costs KMP adds — a per-pattern table build, extra memory, a less predictable inner loop — and name the input shapes where those costs buy you something.

for a senior

Demonstrate that you profile before switching, that you can identify the degenerate input shape in your own data, and that you treat an unbounded quadratic path on untrusted input as a security issue rather than a performance nit.

for a principal

Own the decision as a risk tradeoff across the fleet: the cost of the rewrite and its maintenance burden against the cost of a tail that only fires when someone attacks you, and set the default the whole organisation follows.

## Worst case is not behaviour The strongest reason KMP loses in practice is that the thing it fixes rarely happens. Naive substring search is O(n*m) in the worst case, but that bound describes a specific pathological shape: text and pattern that agree over long stretches. Over varied data — log lines, prose, mixed binary records — a mismatch typically arrives within the first character or two of a window, so the scan does about n comparisons plus change. Big-O is an upper bound; an O(n*m) label does not say the algorithm ever exhibits that behaviour on your input, and treating the label as a prediction is the single most common mistake in this conversation. ## What KMP actually charges you - **Preprocessing per pattern, O(m).** Irrelevant when one fixed signature scans terabytes; material when a service is handed a fresh user-supplied pattern per request and each document is short. In that regime you may spend as long building the table as scanning. - **Memory, O(m).** Small in absolute terms, but it is a per-pattern allocation on a hot path, and if patterns are many and short-lived it is allocation churn for no gain. - **A worse inner loop.** The naive inner loop is compare, advance, branch — trivially predictable and friendly to fast bulk character-search primitives that hardware and runtimes provide. KMP's mismatch path adds a table read whose value is data-dependent, so the loop is less predictable and cannot be reduced to "find the next occurrence of the first character, then compare". Constants matter, and here the constant moves against you. - **Nothing sublinear on offer.** KMP inspects effectively every text character. Skip-based matchers of the Boyer-Moore family can leap over stretches of text and often run several times faster on typical inputs with medium-length patterns — at the price of a worse worst case than KMP's. - **Maintenance.** A hand-written prefix table is a classic off-by-one habitat, and its failure mode is silence: the matcher returns fewer occurrences rather than crashing. Weigh a bespoke matcher in your codebase against a well-tested standard scan that the whole team already trusts. ## When the guarantee is the product Flip the analysis and KMP is the obvious call in three situations. **Self-similar data.** Periodic binary payloads, long runs of a padding byte, protocol frames that repeat a header motif — precisely the shape that makes naive re-scan almost the whole window at every offset. Here the worst case is not hypothetical, it is Tuesday. **Adversarial input.** If the pattern, the text, or both come from outside your trust boundary, an O(n*m) matcher on a request path is an algorithmic-complexity denial-of-service waiting to be found. An attacker submits the degenerate pair and one request burns CPU that should have served thousands. The mitigation is either an input-size cap you can defend or an algorithm with no quadratic case; KMP is the second, and unlike randomized approaches its bound is deterministic, so there is no hash-seed question to answer and no expected-versus-worst asterisk to explain in a review. **Hard latency budgets.** When a component must state a ceiling — a scan that cannot exceed a fixed slice of a frame budget — an algorithm whose cost is bounded by a constant times the input length lets you write the number down. "Fast on typical input" is not a number you can put in a service objective. ## How to answer this out loud The strong answer is a decision rule, not a preference: *default to the well-tested library scan; reach for a linear-guarantee matcher when the input is self-similar or untrusted, or when a bound must be contractual; and measure on real captures before hand-rolling anything.* The weak answer is either "always use KMP, it's O(n+m)" or "never bother, naive is fine" — both replace a workload question with a slogan. If you have a number from a real capture, lead with it; asymptotics decide the tail risk, measurements decide the median.

  • Your search patterns arrive fresh with every request. How does that change the calculus?
    It turns the O(m) preprocessing from a one-off into a per-request tax, and it is paid even when the document is tiny and the match is found in the first few bytes. If documents are short relative to patterns, the table build can dominate, and a scan with no preprocessing wins outright.
  • How would you justify the switch to a linear-guarantee matcher to a skeptical reviewer?
    Frame it as removing a tail risk, not chasing throughput: show a crafted input pair that makes the current scan blow up, express the blowup in CPU-seconds per request, and note the guarantee is deterministic rather than expected. Then show a benchmark on real captures so nobody thinks you claimed a median speedup.
  • Is there a middle option between naive and a linear-guarantee matcher?
    Yes — cap the input sizes so the quadratic term is bounded by construction, or use a skip-based matcher that is faster on typical text while accepting its own worse worst case. Both are legitimate; what is not legitimate is leaving an unbounded quadratic path reachable by untrusted input.

saying these in an interview costs you the question

  • Says KMP is always faster than a naive scan
  • Treats the O(n*m) label as observed behaviour
  • Ignores per-pattern preprocessing on short inputs
  • Dismisses constant factors as irrelevant
  • Never asks who supplies the text or pattern

context