fuzzy: the Sublime Text matcher, reimplemented as one Go file with no dependencies
Go library that provides fuzzy string matching optimized for filenames and code symbols in the style of Sublime Text, VSCode, IntelliJ IDEA et al.
At a glance
- What is it?
- sahilm/fuzzy is a Go library that ranks candidate strings by how well a pattern matches them, in the style of Sublime Text, VSCode and IntelliJ IDEA, and it depends on nothing outside the standard library. It returns the matched character positions so a caller can highlight them, and its scoring is a ranking heuristic rather than a relevance model.
- Who is it for?
- fuzzy fits a Go program that needs a file picker, a command palette, a symbol jump list or a quick open dialog, and it fits a team that does not want to vendor a JavaScript fuzzy matcher or take on a dependency for something this small. It does not fit a search over large free text corpora, where you want relevance rather than prefix friendliness, and it does not fit a user who expects the ranking to understand word boundaries or synonyms.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 121 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Four rules that decide whether a candidate ranks well
The scoring is the whole library, and it is described in four rules.
Results are returned in descending order of match quality, and quality is determined by four conditions. The first character in the pattern matches the first character in the match string. The matched character is camel cased. The matched character follows a separator character such as _. And the matched character is adjacent to a previous match.
Read as a whole, that is a description of how a programmer types. You type the first letter of what you want, you type the capital letter of a camel cased identifier, you type the letter after a _ separator, and you type consecutive characters of a word. Each of those four things is a signal that the candidate is what you meant, and they are combined into a score.
That is the point where this library differs from a general text search. A text search engine wants to rank by relevance, and it has an index, a tokeniser, a scoring function tuned over a corpus. This library wants to rank a hundred thousand filenames by how plausibly you meant each one, and it does that in one pass with no index.
The two lowest rules are the ones that make it feel like a code search rather than a text search. A pattern that starts at a camel case boundary or after a _ separator gets a better score than the same characters typed in the middle of a word, which is why typing gnr finds moduleNameResolver rather than a file called designator.
The library is Unicode aware, and the credits give the reason. Unicode awareness and various performance optimisations were contributed by two named contributors, and the algorithm itself is based on the work of forrestthewoods, with a link to the reverse engineering of Sublime Text's fuzzy match and the blog post describing the algorithm in detail.
One file, no dependencies, and a deliberate module layout
The repository is unusually small and the module definition is the evidence.
The top level holds fuzzy.go, fuzzy_test.go, a go.mod, a go.sum, a Makefile, a CONTRIBUTING.md, a LICENSE, a README.md, an assets/ directory with the images, a _example/ directory, a testdata/ directory, and a .github/ directory.
The go.mod names the module github.com/sahilm/fuzzy, requires Go 1.24.5, and lists exactly one dependency, github.com/kylelemons/godebug at v1.1.0, with a comment marking it as a test dependency. The README puts it more bluntly: this library is external dependency-free, it only depends on the Go standard library.
So the runtime cost of adding fuzzy to a project is a copy of one source file and a set of standard library imports. There is no transitive tree to audit, no version constraints to track beyond the Go version, and nothing that can break under a dependency bump elsewhere in your build.
The testdata/ directory and the test dependency point at how the library is tested. Rather than only testing the function against hard coded cases, the repository carries a corpus, and the demo is described as a demo of matching various patterns against roughly 16K files from the Unreal Engine 4 codebase.
The Makefile shows the rest of the workflow. The default target is setup, lint and test, and there are targets for goimports, golangci-lint, cover, ci, install and build. The tools are pinned, with goimports installed at v0.46.0 and golangci-lint at v2.12.2, both into the Go bin directory. The cover target is more elaborate than most, running the tests per package, merging the cover profiles and then producing an HTML report with go tool cover.
Matching a slice of strings, and where the highlight comes from
The simplest call takes a pattern and a slice of strings, and returns matches with the positions of the matched characters, which is what lets a caller render the result.
The usage example in the README prints the matches with the matched characters in bold, and the pattern and data are chosen to show the three scoring rules at work.
pattern := "mnr"
data := []string{"game.cpp", "moduleNameResolver.ts", "my name is_Ramsey"}
matches := fuzzy.Find(pattern, data)
for _, match := range matches {
for i := 0; i < len(match.Str); i++ {
if contains(i, match.MatchedIndexes) {
fmt.Print(fmt.Sprintf(bold, string(match.Str[i])))
} else {
fmt.Print(string(match.Str[i]))
}
}
fmt.Println()
}That example produces three results, and the ordering is the lesson. moduleNameResolver.ts matches because the first character of the pattern is the first character of the match, the n and r are camel cased boundaries, and the characters are adjacent. my name is_Ramsey matches because the r follows a _ separator and the m and n are adjacent, but it scores lower. game.cpp matches the g and the r but scores lower still.
The MatchedIndexes field is the part that makes this usable in a UI. It is a list of positions in the candidate string, so a terminal program, a fuzzy finder or a web front end can highlight exactly the characters that matched, which is the visual shorthand that makes fuzzy finders feel fast.
Results are sorted by match quality by default, and each function has a NoSort variant that skips sorting, named FindNoSort, FindFromNoSort and FindFromIterNoSort.
The reason to have those is worth stating. Sorting requires collecting every match before returning anything, and if you are matching a very large candidate set and only intend to show the first few results, the NoSort variant lets you stream instead of materialising the whole list.
FindFrom and FindFromIter, for candidates that are not strings
Most real use cases do not have a slice of strings, and the library handles that with two mechanisms.
If your data is not a slice of strings, you can use FindFrom by implementing the provided Source interface. The README's example defines an employee struct with a name and an age, a named slice type employees, and implements String(i int) string and Len() int on it, so that the employee names are what get matched while the original records stay available.
type employees []employee
func (e employees) String(i int) string {
return e[i].name
}
func (e employees) Len() int {
return len(e)
}
results := fuzzy.FindFrom("al", emps)
for _, r := range results {
fmt.Println(emps[r.Index])
}The result carries an Index, so the caller gets the position in the original slice and can look up the full record. That is the important detail. The library matches on a string projection of your data and hands back the index, so the candidate type does not have to be a string and the matched data does not have to be what you display.
The second mechanism is for data produced lazily. If your data is produced lazily, you can use FindFromIter to match against a Go iterator, an iter.Seq[string], instead of a Source. That fits a case where the candidate list is expensive to build, for instance a file listing that has to be walked, or a search over a remote index that should not be materialised.
The design here is the part worth copying. The library does not decide how you represent your data, and it does not insist on a slice. It asks for the smallest thing it needs, a string and an index, and it offers the lazy variant for the case where even that is too much.
Speed, as measured on file lists
The performance claim in the README is specific and comes with numbers, which is more useful than a general statement about speed.
The library says matches are returned in milliseconds, and that this makes it perfect for interactive search boxes. The benchmark section gives two results on what it calls a normal laptop.
BenchmarkFind/with_unreal_4_(~16K_files)-4 100 12915315 ns/op
BenchmarkFind/with_linux_kernel_(~60K_files)-4 50 30885038 ns/opAnd the summary in the prose: matching a pattern against roughly 60K files from the Linux kernel takes about 30ms.
So the numbers to take away are these. About 13 milliseconds for 16K candidates, about 31 milliseconds for 60K. The scaling is close to linear, roughly doubling the candidate count roughly doubles the time, which is what you would expect from a single pass over the candidate set with no index.
That is fast enough for a keystroke driven search box, which is the use the library names. It also sets the boundary of the approach. The work is proportional to the number of candidates, so a corpus of a million candidates would be in the hundreds of milliseconds and would begin to feel sluggish on every keystroke. If your candidate set is that large, you want an index with a prefix or trigram lookup, and you would be using a different kind of library.
For a fuzzy finder over a source tree, a project directory or a list of symbols, tens of thousands of candidates is the normal case, and this is comfortably fast enough.
Where a fuzzy matcher is the wrong tool
Two limits, and the second one is the important one.
The first is scale, as above. The scoring is a linear scan with a constant factor, so the library is for candidate sets you can scan on every keystroke. Tens of thousands is right, and hundreds of thousands is at the edge.
The second is that the scoring is tuned for identifiers, and using it on prose gives results that look wrong. The four rules are all about code structure. A camel case boundary means nothing in a paragraph of English. A separator character such as _ means nothing either. So a pattern typed against prose will match on adjacency and on the first character rule, and the ranking will reflect the accident of where the letters fall in the text rather than any sense of importance.
This is not a criticism of the library, which says outright that it is optimized for filenames and code symbols, in the style of Sublime Text, VSCode and IntelliJ IDEA. It is a statement of what the library is for. A tool that ranks documents by relevance, with an inverted index, tokenisation and a corpus-tuned scoring function, is a different tool answering a different question, and no amount of configuration makes the one do the job of the other.
The third limit is narrower. The library returns matches and their positions. It does not tell you why a candidate matched, and there is no way to tune the four rules or to add your own signal. If your use case needs a relevance model that reflects your domain, you will be writing that yourself, and fuzzy will have given you the highlighting and the ordering but not the judgement.
Editorial conclusion
fuzzy fits a Go program that needs a file picker, a command palette, a symbol jump list or a quick open dialog, and it fits a team that does not want to vendor a JavaScript fuzzy matcher or take on a dependency for something this small. It does not fit a search over large free text corpora, where you want relevance rather than prefix friendliness, and it does not fit a user who expects the ranking to understand word boundaries or synonyms. Check three things before you commit. Check whether your candidates are identifiers and filenames, because that is the case the scoring is tuned for, and a prose document matched with the same library will rank surprisingly. Check that you want a single pass over a fixed candidate set rather than an index, since the library takes a slice and returns matches for it. And check whether you need the NoSort variants, because the library sorts by match quality by default and FindNoSort, FindFromNoSort and FindFromIterNoSort exist for the case where you want to stream instead. The last push was on 2026-06-24, the newest release listed is v0.1.3 from 2026-06-11, and the license is MIT.
Frequently asked questions
How do I install the fuzzy Go library?
Run go get github.com/sahilm/fuzzy, or use your favorite dependency management tool. The library itself has no external dependencies and only depends on the Go standard library, and the module requires Go 1.24.5.
How does the fuzzy library decide which results are best?
Results are returned in descending order of match quality, and quality is determined by four conditions: the first character in the pattern matching the first character in the match string, the matched character being camel cased, the matched character following a separator character such as _, and the matched character being adjacent to a previous match.
Can fuzzy match against data that is not a slice of strings?
Yes. You can use FindFrom by implementing the provided Source interface, with a String(i int) string method and a Len() int method, and the results carry an Index back into your original data. If your data is produced lazily, FindFromIter matches against a Go iterator, an iter.Seq[string], instead of a Source.
How fast is the fuzzy library?
The README benchmarks on a normal laptop show about 13ms for roughly 16K files from the Unreal Engine 4 codebase and about 31ms for roughly 60K files from the Linux kernel, with matches returned in milliseconds, which the project says makes it suitable for interactive search boxes.
Does fuzzy sort its results, and can I turn that off?
Results are sorted by match quality by default, and each function has a NoSort variant that skips sorting, named FindNoSort, FindFromNoSort and FindFromIterNoSort.
What license is the fuzzy library released under?
MIT, with the licence text in the LICENSE file and the copyright held by Sahil Muthoo from 2017. The newest listed release is v0.1.3 from 2026-06-11, and the last push was on 2026-06-24.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sahilm-fuzzy)