Wait, What?
A substring search can guarantee linear worst-case time without storing a prefix table, suffix table or automaton.
The Two-Way string-matching algorithm, developed by Maxime Crochemore and Dominique Perrin, looks unusual because it does not begin by building a large auxiliary structure. Instead, it studies the pattern itself. A carefully chosen cut divides the pattern into two parts. The algorithm compares around that cut in a way that uses the pattern’s period to skip positions safely.
For a beginner, this is a lesson about repeated structure in strings. At intermediate level, the key concepts are periods, suffix order and a critical factorization. At advanced level, you need to understand why the right half is tested first and why the resulting shifts cannot skip a valid match. At professional level, the real work includes byte versus Unicode semantics, empty-pattern contracts, periodic worst cases, overflow-safe indexing, library fast paths and differential testing.
Quick Answer
Learn the Two-Way algorithm in this order: exact substring search → pattern periods → split a pattern at a cut → maximal suffixes under two alphabet orders → critical factorization → compare the right half first → verify the left half only after the right succeeds → use the period to shift safely → add memory for the periodic case → understand the nonperiodic case → test against a naive matcher → handle bytes, Unicode and boundary contracts → benchmark against mature library implementations.
1. The exact job
Given a text T of length n and a pattern P of length m, find the first position s such that:
T[s : s+m] == P
or report that no such position exists.
This article discusses exact matching. Case-insensitive search, locale-aware search, approximate matching and regular expressions are different jobs.
2. The naive algorithm is the right first model
for s from 0 to n-m:
j = 0
while j < m and T[s+j] == P[j]:
j += 1
if j == m:
return s
This is easy to reason about, but highly repetitive patterns can make it perform many repeated comparisons.
The Two-Way algorithm asks: what structural fact about the pattern lets us prove that several candidate starts can be skipped at once?
3. Periods are the central structural idea
A positive integer p is a period of a string P when characters repeat after a displacement of p wherever both positions exist:
P[i] == P[i+p]
for every valid comparison position.
For example, ababab has period 2. The pattern aaaaaa has period 1.
A smaller period means more repetition. Repetition is precisely what makes ordinary left-to-right matching vulnerable to rechecking the same evidence.
4. Split the pattern at a cut
Write the pattern as:
P = U · V
where U is the left part and V is the right part.
Not every cut is equally useful. The Two-Way algorithm finds a critical factorization: a cut where the local periodic behavior around the cut exposes the global period needed for safe shifting.
The critical factorization theorem is the mathematical engine behind the algorithm.
5. Why maximal suffixes appear
A practical linear-time way to find a useful critical factorization is to compute a maximal suffix twice: once under the normal alphabet order and once under the reverse order.
A maximal suffix procedure compares pattern suffixes without sorting all of them. It advances a small set of indices and simultaneously discovers a candidate period.
You do not need a suffix array. The preprocessing remains O(m) time with O(1) extra algorithmic state.
6. Two alphabet orders protect against one-sided bias
If we search only for the maximal suffix under <, the chosen cut may not expose the strongest factorization. Repeating the process under the opposite order > gives the complementary candidate.
The algorithm selects the better of the two cuts and carries forward its associated period estimate.
This is a good example of a professional habit: when a proof depends on order, verify both orientations rather than assuming one is sufficient.
7. The search compares the right half first
At candidate alignment s, the algorithm begins comparisons near the critical cut and moves rightward through V.
If the right half mismatches, the structure of the critical factorization tells us how far the pattern can shift without missing a match.
Only if the right side matches does the algorithm verify the left side U.
This is why the method is called Two-Way: the pattern is verified in two directional phases around the factorization.
8. High-level structure
(cut, period) = critical_factorization(P)
if P has the required periodic relation around cut:
return periodic_search(T, P, cut, period)
else:
return nonperiodic_search(T, P, cut, period)
The difficult part is not memorizing the names of the two branches. It is understanding what evidence lets each branch shift safely.
9. The periodic case uses memory
Suppose a periodic pattern has already matched a region that will remain known after shifting by one full period.
Rechecking that region would waste work. The periodic branch therefore remembers how much of the left part is already guaranteed by the previous successful comparisons.
This small “memory” value is one reason the algorithm can avoid quadratic behavior on patterns such as:
aaaaaaaaaaaaaaaaab
or long alternating strings.
10. Memory is not a memoization table
The algorithm does not store a value for every pattern position. It retains only a constant amount of state describing a guaranteed matched region after a period shift.
That distinction matters when people describe the algorithm as constant-space.
Constant-space means O(1) auxiliary algorithmic storage beyond the input and ordinary scalar variables. A concrete library may still use implementation metadata, vector registers, guard values or wrapper objects.
11. The nonperiodic case can shift more aggressively
When the pattern does not satisfy the periodic relation required by the periodic branch, there is no need to preserve a remembered repeated prefix in the same way.
A mismatch can often justify a shift derived from the larger side of the critical factorization.
The exact formulas depend on the implementation convention, but the conceptual rule is stable: use the factorization proof to skip candidate starts that cannot possibly match.
12. Why worst-case time is linear
The original Crochemore–Perrin result gives linear-time pattern preprocessing and linear-time searching while using constant extra space.
The proof does not say “each text character is compared exactly once.” Some positions may be involved more than once. Instead, it bounds how the search index and comparison index advance so that total comparison work remains proportional to n+m.
This is an important algorithms lesson: linear total work can come from amortized progress, not from forbidding every repeated operation.
13. A tiny periodic example
Let:
P = ababab
T = xxababababyy
The period of P is 2. Once a substantial periodic region has been verified, shifting by 2 preserves knowledge about overlapping characters.
A naive matcher may repeatedly rediscover the same ab structure from nearby starts. The Two-Way algorithm turns that repetition into a reason to avoid redundant comparisons.
14. A nonperiodic example
Consider:
P = abracad
The pattern has less useful short-period repetition. The critical factorization still gives a cut from which mismatches can justify safe movement.
The algorithm is therefore not “only for periodic strings.” Periodicity changes the search branch, but both branches use the same structural preprocessing.
15. Compare Two-Way with KMP
Knuth–Morris–Pratt also guarantees O(n+m) time. KMP preprocesses the pattern into a prefix-function or failure table and uses that table to determine how much matched prefix survives a mismatch.
Two-Way instead uses critical factorization and periods and can operate with O(1) auxiliary algorithmic storage.
For teaching, this comparison is useful because both algorithms solve repeated-work problems but compress their knowledge differently.
16. Compare Two-Way with Boyer–Moore
Boyer–Moore-family algorithms often compare from the pattern’s right end and use bad-character and/or good-suffix information to make large practical shifts.
Two-Way’s appeal is a strong worst-case bound with tiny auxiliary state. Real libraries may combine ideas or use short-pattern fast paths before switching to a Two-Way core.
The professional question is therefore not “which named algorithm always wins?” It is “which implementation matches this text distribution, pattern length and machine?”
17. Compare Two-Way with Bitap and Aho–Corasick
Bitap represents matching state with machine-word bit operations and is especially elegant for short patterns that fit the chosen word representation. Aho–Corasick builds a trie plus failure links to match many patterns at once.
Two-Way owns a different job: one exact pattern, linear worst-case search, constant auxiliary algorithmic space.
Those distinctions prevent algorithm names from becoming interchangeable buzzwords.
18. Empty-pattern behavior is an API contract
Mathematically, the empty string is a substring at every boundary. Programming libraries choose a concrete convention, commonly returning the beginning of the text.
Define the behavior before entering the core algorithm. Do not let m=0 fall accidentally into period or indexing formulas that assume at least one pattern element.
19. One-character patterns deserve a fast path
When m=1, a specialized byte/character search is simpler and often maps to highly optimized library primitives.
A production substring routine may therefore have a dispatch shape such as:
if m == 0: handle_empty()
if m == 1: find_single_element()
if m is very small: use tuned short-pattern path
else: use Two-Way core
The existence of fast paths does not weaken the algorithm. It reflects the reality that asymptotic elegance and constant factors are different dimensions.
20. Bytes and Unicode are not the same search problem
A byte-oriented Two-Way implementation compares byte values. That is appropriate for binary buffers and for exact UTF-8 byte substring search.
But a user-visible “character” may be a Unicode code point or a grapheme cluster composed of several code points. Canonically equivalent strings can even have different byte sequences.
Before promising linguistic text matching, specify whether the sequence elements are bytes, code points, normalized code points or grapheme clusters.
21. Case folding can change length
Case-insensitive Unicode matching is not always a one-character-to-one-character transformation. Some folds expand or normalize text.
Do not simply lowercase individual bytes and feed them to an exact matcher. If transformation changes positions, returned offsets need a mapping back to the original text.
That is a text-processing design problem around the exact search algorithm.
22. Signed character values can break ordering assumptions
In low-level languages, a plain char may be signed or unsigned. Maximal-suffix preprocessing relies on a consistent alphabet ordering.
For byte strings, compare values in an explicit unsigned domain when that is the intended alphabet. Otherwise, bytes above 127 can behave differently across platforms.
23. Bounds arithmetic must not overflow
Expressions such as s + m, n - m and index shifts can overflow fixed-width unsigned or signed integers if written carelessly.
Check m > n before computing n-m. Use size types consistently. Verify that shift calculations cannot wrap around.
Substring search is memory-safety-sensitive code in systems libraries; arithmetic is part of correctness.
24. Streaming search changes the boundary problem
If text arrives in chunks, a match may begin near the end of one chunk and finish in the next.
A one-shot Two-Way routine cannot see across a missing boundary. A streaming wrapper must retain enough suffix context—up to m-1 elements—or use a stateful search design that preserves equivalent information.
Again, the core algorithm and the surrounding interface are separate jobs.
25. A strong teaching sequence: predict → trace → explain → modify → build
Programming-education research supports worked examples, explicit subgoals and structured movement from reading code toward independent construction.
- Predict: identify a short period in a pattern.
- Trace: mark a proposed critical cut and show the two comparison directions.
- Explain: say why a mismatch permits a particular shift.
- Modify: change one pattern character and predict whether the period survives.
- Build: implement preprocessing and search only after the invariants can be verbalized.
This fits the broad PRIMM progression—Predict, Run, Investigate, Modify, Make—and the evidence for subgoal-labeled worked examples in introductory programming.
26. Teach the maximal-suffix scan separately
Do not introduce critical factorization, maximal suffix, periodic memory and the entire search loop in one code listing.
First give learners a maximal-suffix trace table:
step | candidate suffix | comparison position | relation | next indices | period candidate
Then repeat under the opposite alphabet order. Only after the preprocessing output is visible should it feed the matcher.
27. Differential testing is the professional baseline
Write a trusted naive matcher and compare it with the Two-Way implementation on generated inputs:
two_way(T, P) == naive_find(T, P)
Test every possible short text and pattern over a tiny alphabet such as {a,b} up to manageable lengths. Exhaustive small-state testing catches surprising boundary errors.
28. Adversarial patterns matter
Include:
- all-equal strings such as
aaaa...; - long periodic patterns such as
abababab...; - nearly periodic patterns ending in one different symbol;
- pattern longer than text;
- match at position 0 and at the final legal position;
- no match after a long partial prefix;
- binary buffers containing zero bytes;
- random byte arrays;
- very long patterns near allocation and index limits.
29. Test the factorization itself
Do not wait for a full search failure to debug preprocessing. For small patterns, independently compute periods or verify candidate cuts by brute force.
Useful assertions include:
- the cut lies within the pattern;
- the reported period is positive;
- the periodic branch is entered only when its structural precondition holds;
- every search shift is positive;
- the candidate start never moves backward;
- no comparison reads outside text or pattern bounds.
30. Production implementations often hybridize
GNU C Library source has long used a Two-Way-based strategy for substring search, with comments describing short-needle and long-needle variants. Other C libraries have also adopted Two-Way approaches.
This is useful professional evidence: a theoretically sophisticated string algorithm can survive in systems code because it combines a robust worst-case guarantee with small auxiliary state.
But always benchmark the implementation you actually ship. Vectorized short-pattern search or architecture-specific primitives can dominate for many real inputs.
31. Beginner-to-professional learning ladder
- Beginner: identify repetition and periods in short patterns.
- Foundation: understand cuts, suffix ordering and why matching can start near a critical cut.
- Intermediate: implement maximal-suffix preprocessing and a correct Two-Way matcher.
- Advanced: explain periodic memory, nonperiodic shifts and the linear-time argument.
- Professional: define byte/Unicode semantics, harden bounds arithmetic, support streaming boundaries, fuzz adversarial periodic inputs, cross-check mature libraries and benchmark hybrid dispatch strategies.
32. Ownership boundary
This article owns the public learning job for the Crochemore–Perrin Two-Way exact string-matching algorithm: critical factorization, maximal suffixes, periods, two-direction verification, constant auxiliary space and production search semantics. It complements broader string-matching material and separate articles on Aho–Corasick, Bitap, Booth and other algorithms. It does not redefine learner-state systems, assessment calibration, studying interfaces or any private implementation machinery elsewhere in the eduKate ecosystem.
Sources and further reading
- Maxime Crochemore and Dominique Perrin, “Two-Way String-Matching,” Journal of the ACM 38(3), 1991: DOI.
- Author-hosted paper copy: Crochemore–Perrin PDF.
- GNU C Library Two-Way substring-search source as mirrored by Debian Sources: str-two-way.h.
- ACM/IEEE-CS/AAAI CS2023, Algorithms and Complexity knowledge area: CS2023.
- Computer Science Teachers Association, 2026 standards overview emphasizing reading, evaluating, modifying, debugging and creating algorithms and programs: CSTA.
- Sentance, Waite and Kallia, PRIMM programming pedagogy: SIGCSE.
- Margulieux, Morrison and Decker, subgoal-labeled worked examples in introductory programming: International Journal of STEM Education.
Professional rule: you understand Two-Way matching when you can explain how the pattern’s own periodic structure proves that a skipped text position cannot contain a match.
