Naive String Matching
Try every alignment of the pattern against the text and compare character by character.
Overview
Naive matching finds every occurrence of a pattern p (length m) inside a text t (length n) by placing p at each of the n - m + 1 possible start positions and comparing left to right until a mismatch or a full match.
It is the baseline every other string-matching algorithm is measured against. In practice it is surprisingly competitive on natural-language text, because a random alignment usually fails on the first or second character. Its worst case, O(nm), appears on highly repetitive inputs such as t = "aaaa…a" and p = "aaa…ab".
Intuition
A mental model before the formal terms.
Imagine a stencil of the pattern that you slide along the text one cell at a time. At each stop you read the letters through the stencil from left to right; the moment one letter disagrees you stop reading and slide the stencil one cell further. The stencil has no memory — after a failure it forgets everything it just saw, which is exactly the inefficiency Knuth–Morris–Pratt (KMP) removes.
How it works
- For each start index
ifrom0ton - m, setj = 0. - While
j < mandt[i + j] == p[j], incrementj. - If
jreachedm, report a match ati. Otherwise (or after reporting) continue withi + 1. - The scan at a given
ican stop early at the first mismatch, which is why the average case on non-repetitive text is close toO(n).
Why it works
Every occurrence of p in t starts at some index i ≤ n - m, and the inner loop at that i checks exactly the characters t[i..i+m-1] against p[0..m-1]. Since all start positions are tried, no occurrence can be missed and no false match can be reported.
The cost bound is (n - m + 1) · m comparisons. On random text over an alphabet of size σ the expected comparisons per alignment is below 1/(1 - 1/σ), i.e. about 1.04 for the Latin alphabet, so the expected total is roughly n.
Recognition
How to tell a problem wants this.
- The pattern is short (
m ≤ 10) or the text is small enough thatn · mis a few million operations. - You need a correct answer quickly in an interview before optimising — write this first, then discuss Knuth–Morris–Pratt (KMP) or Rabin–Karp.
- Inputs are natural language or otherwise non-repetitive, so worst-case behaviour will not occur.
Interactive visualization
Play, step, change the input. ← → and space work too.
1for s in 0 .. n-m:2 j = 03 while j < m and text[s+j] == pattern[j]:4 j += 15 if j == m: report match at s6return matchesPseudocode
1for i in 0 .. n - m:2 j = 03 while j < m and t[i + j] == p[j]:4 j += 15 if j == m: report match at iImplementations
1def naive_search(text: str, pattern: str) -> list[int]:21 · Handle edge cases3 n, m = len(text), len(pattern)4 matches: list[int] = []5 if m == 0:6 return list(range(n + 1))72 · Try every alignment8 for i in range(n - m + 1):93 · Compare characters left to right10 j = 011 while j < m and text[i + j] == pattern[j]:12 j += 1134 · Record a full match14 if j == m:15 matches.append(i)16 return matchesrange(n - m + 1)produces exactly the valid start positions; whenm > nthe range is empty and the function returns[]without any special case.- The empty pattern returns
list(range(n + 1))— every position, matching the convention ofstr.find(""). text[i + j] == pattern[j]compares single-character strings; Python interns short strings so this is cheap, but still slower than a C loop.j == mafter thewhilemeans the full pattern matched ati.
A per-character Python loop is roughly 50–100× slower than str.find, which runs the same idea in C.
str.find(pattern, start)and theinoperator implement substring search in C (a two-way / Crochemore–Perrin variant since 3.10), so prefer them outside of an interview.- Python strings are immutable sequences of Unicode code points;
lencounts code points, so surrogate pairs are not an issue as they are in JS. - Slicing
text[i:i + m] == patterncopiesmcharacters each time — fine for smallm, but it is an O(m) allocation per alignment.
- Using
range(n - m)(missing the+ 1) and dropping a match that ends exactly at the last character. - Comparing
text[i + j]without thej < mguard first —andshort-circuits, so the order of the two conditions matters. - Returning after the first match when all occurrences are requested.
- String length: Python
lencounts Unicode code points; JS/TS.lengthcounts UTF-16 code units; C++std::string::size()counts bytes. Alignments can split a multi-byte or surrogate-pair character in C++ and JS/TS. - C++
size_tis unsigned:text.size() - pattern.size()wraps when the pattern is longer, so cast tointor write the bound asi + m <= n. - Out-of-range access is undefined behaviour in C++ (
operator[]), returnsNaNfromcharCodeAtin JS/TS, and raisesIndexErrorin Python. - Every language ships a C-level substring search (
std::string::find,indexOf,str.find) that should be used outside interviews.
Complexity
Average assumes non-repetitive text; worst case is repetitive text like "aaaa…a" vs "aa…ab".
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Short patterns or small texts where
n · mis cheap. - Natural-language text where mismatches happen within one or two characters.
- As a correctness oracle when testing a faster matcher.
- Repetitive inputs (DNA, binary strings, long runs) where the
O(nm)worst case is realistic — use Knuth–Morris–Pratt (KMP) or Z-Algorithm. - Many patterns against one text — use Aho–Corasick.
- Many queries against one fixed text — preprocess the text with a Suffix Array instead.
Alternatives
Common mistakes
- Looping
iton - 1instead ofn - mand reading past the end of the text. - Forgetting the empty-pattern case (matches at every position by convention).
- Restarting the inner comparison from
i + 1after a full match but then also skippingmcharacters — overlapping matches like"aa"in"aaa"would be lost.
Interview patterns
strStr/indexOf: return the first occurrence — naive is accepted, but be ready to explain KMP.- Counting occurrences of a short word in a long text.
- Baseline for "is
ta rotation ofs?" by searchingtinsides + s.
- Recognizing the approach from an array and a targetIntermediate
- Deciding whether O(n²) can be improvedIntermediate
- Recognizing a sliding-window problemIntermediate
- Minimum Size Subarray SumIntermediate
- Longest Substring Without Repeating CharactersIntermediate