How fuzzy search works: typo-tolerant matching explained
· 8 min read · by Jaime Guzman
Type hxvault into a good command palette and it finds helix_vault. Type mainrs and it finds src/main.rs. No wildcards, no exact spelling, and often a typo or two forgiven. This behavior - fuzzy search - has become the default way developers and power users navigate everything from code editors to file managers. Here is how it actually works, with a live demo you can type into.
Try it first
This is a real fuzzy matcher running in your browser - a miniature version of the algorithm family used by tools like fzf, VS Code's quick-open, and Shuffle's command palette. The highlighted letters show which characters matched; the number is the match's score.
- Documents/Projects/helix_vault/notes.md37
Step 1: subsequence matching
The core rule is simpler than most people expect: every character of the query must appear in the candidate, in order - but not necessarily adjacent. hxvault matches helix_vault because you can walk left to right and find h, then x, then v-a-u-l-t. That check is a single linear scan per candidate: for each query character, find its next occurrence in the text. If any character can't be found, the candidate is out.
Subsequence matching alone is binary - match or no match. The magic that makes fuzzy search feel smart is what happens next.
Step 2: scoring - the interesting part
A query like doc matches hundreds of paths. The ranking is what decides whether the one you meant is at the top. Practical scorers reward and penalize a handful of things:
- Consecutive runs. Matching
d-o-cas an unbroken run inDocumentsis worth much more than three scattered letters. Real typing tends to produce prefixes and runs, so runs are strong intent signals. - Word boundaries. A hit on the first letter after a
/,_,-,., or a lowercase-to-uppercase transition (camelCase) scores a bonus. This is whyffmfindsfast-file-manager: three boundary hits beat any buried substring. - Gap penalties. Every character skipped between matches costs a little. Tight matches beat sprawling ones.
- Length bias. All else equal, shorter candidates win - matching 5 of 10 characters is more meaningful than 5 of 80.
The demo above implements exactly these four rules in about thirty lines. Production matchers refine the weights - fzf's v2 algorithm is a variant of Smith-Waterman alignment, borrowed from bioinformatics, which uses dynamic programming to find the optimal assignment of query characters to positions rather than the greedy first-occurrence walk. Greedy is faster; alignment ranks better when a character could match in several places.
Step 3: typo tolerance
Strict subsequence matching fails the moment you transpose two letters - hlexi no longer matches helix. Tools that forgive typos bring in a second concept: edit distance. The Levenshtein distance between two strings is the minimum number of single-character insertions, deletions, and substitutions to turn one into the other (the Damerau variant adds transpositions, the most common real typo). A typo-tolerant matcher accepts candidates within a small distance budget - usually 1 for short queries, 2 for longer ones - and applies a score penalty per correction, so exact matches still rank first.
Computing edit distance for every candidate is more expensive than a linear scan, so engines use tricks to avoid doing it often: try the cheap subsequence pass first and only fall back to fuzzy-with-typos when it yields nothing, or use banded dynamic programming that abandons a candidate as soon as its minimum possible distance exceeds the budget.
Making it fast at file-system scale
A code editor fuzzy-searches a few thousand project files. A file manager's palette searches your home directory - easily half a million paths. At that scale the per-candidate cost dominates, and three engineering choices matter:
- Index up front. Walk the directory tree once in the background and keep the paths in a compact in-memory list, rather than hitting the file system per keystroke.
- Filter cheaply, score selectively. The binary subsequence test eliminates most candidates in nanoseconds; only survivors get the expensive scoring pass.
- Search incrementally. When the query grows from
doctodocu, every match of the longer query is necessarily a match of the shorter one - so you can search within the previous result set instead of the full corpus.
Do all three and ranking half a million paths takes single-digit milliseconds - which is why a well-built palette can re-rank the whole list on every keystroke and still feel instant. That budget question - what has to happen inside one frame - is a topic of its own; we cover it in What makes a file manager fast?
Where you've already used this
Fuzzy matching quietly became a standard interface: Sublime Text's Goto Anything popularized it in 2008, VS Code's ⌘P made it universal, fzfbrought it to the shell, and launchers like Alfred and Raycast brought it to the whole OS. File managers were late to the party - Finder still offers only exact-prefix "type-select" and full Spotlight search, with nothing in between. Shuffle's ⌘P palette exists precisely to fill that gap: typo-tolerant fuzzy search over your home directory, with the same subsequence-plus-boundaries scoring you just played with above - tuned so results update in about a millisecond.
The recipe - match query characters as an in-order subsequence; score runs, word boundaries, gaps, and length; forgive small edit distances at a penalty; index once and re-rank per keystroke. Thirty lines gets you 80% of the way - the last 20% is weights and speed.