<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohana Krishna</title>
    <description>The latest articles on DEV Community by Mohana Krishna (@mohankrishna).</description>
    <link>https://dev.to/mohankrishna</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3979044%2F5549192e-068b-4632-ad0d-5f3f86ae2e38.png</url>
      <title>DEV Community: Mohana Krishna</title>
      <link>https://dev.to/mohankrishna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohankrishna"/>
    <language>en</language>
    <item>
      <title>I Replaced a 461-Million-Downloads-a-Month Glob Package With One Rust File</title>
      <dc:creator>Mohana Krishna</dc:creator>
      <pubDate>Mon, 07 Sep 2026 17:11:47 +0000</pubDate>
      <link>https://dev.to/mohankrishna/i-replaced-a-461-million-downloads-a-month-glob-package-with-one-rust-file-5a36</link>
      <guid>https://dev.to/mohankrishna/i-replaced-a-461-million-downloads-a-month-glob-package-with-one-rust-file-5a36</guid>
      <description>&lt;p&gt;I wanted a boring command.&lt;/p&gt;

&lt;p&gt;Give it a filesystem query, let it find the right files, and get out of the way. It should start quickly enough to use in scripts, stream results instead of building a giant array, stop after the first useful answer, and avoid crawling directories that obviously cannot match.&lt;/p&gt;

&lt;p&gt;That sounds like one job. In practice, it often becomes a small dependency stack.&lt;/p&gt;

&lt;p&gt;There is a glob package for patterns, a directory walker for traversal, an ignore package for &lt;code&gt;.gitignore&lt;/code&gt;, an argument parser for the CLI, a serializer for machine-readable output, and sometimes a task pool or command runner on top. Each piece is reasonable on its own. The combined result is less appealing: more startup work, more code in the supply chain, more memory, and several independent stages that know nothing about each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm3rzfovnvv29ue3edprq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm3rzfovnvv29ue3edprq.png" alt="A conventional glob pipeline opens the whole filesystem tree before filtering, while Branchcut compiles the query and prunes branches before opening them." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The difference Branchcut is built around: filter after walking, or compile enough knowledge to avoid the walk.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The walker does not know that the matcher only cares about &lt;code&gt;packages/core/src&lt;/code&gt;. The matcher does not know that the caller will stop after ten results. An exclusion may reject &lt;code&gt;node_modules&lt;/code&gt;, but only after the traversal has already entered it.&lt;/p&gt;

&lt;p&gt;That was the reason I created &lt;a href="https://github.com/codex-mohan/branchcut" rel="noopener noreferrer"&gt;Branchcut&lt;/a&gt;: I wanted the entire query to become one traversal plan.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://zerodepshack.com/" rel="noopener noreferrer"&gt;Zero Dependency hackathon&lt;/a&gt; supplied the constraint that made the idea interesting: Rust standard library only, an empty dependency manifest, and no vendored implementation hiding behind it. Branchcut ended up as a 2,231-line &lt;code&gt;src/main.rs&lt;/code&gt;, zero crates, and a release executable that is about 356 KiB on my current Windows build.&lt;/p&gt;

&lt;p&gt;The size varies by platform and toolchain. The important part is that the executable is self-contained. There is no runtime, package directory, or transitive dependency tree to carry with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The package I set out to replace
&lt;/h2&gt;

&lt;p&gt;The direct Package Killer target is &lt;a href="https://www.npmjs.com/package/fast-glob" rel="noopener noreferrer"&gt;&lt;code&gt;fast-glob@3.3.3&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is not a toy chosen because it would be easy to beat. The official npm downloads API recorded 5,536,931,736 downloads from September 2025 through August 2026. That works out to an average of &lt;a href="https://api.npmjs.org/downloads/point/2025-09-01:2026-08-31/fast-glob" rel="noopener noreferrer"&gt;461,410,978 downloads per month&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fast-glob&lt;/code&gt; is mature and supports more syntax and configuration than Branchcut. I was not interested in cloning its JavaScript API or pretending that three days of work had replaced years of compatibility knowledge. I wanted to replace the common workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;extraPredicates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Branchcut expresses that work as one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;branchcut &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--glob&lt;/span&gt; &lt;span class="s1"&gt;'packages/**/src/**/*.{rs,ts}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'**/{target,node_modules,dist}/**'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; file &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--limit&lt;/span&gt; 100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stats&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is not that Rust has a faster &lt;code&gt;*&lt;/code&gt; loop than JavaScript. The point is that the compiler now sees the positive patterns, exclusions, type filter, hidden-file policy, and termination condition together.&lt;/p&gt;

&lt;p&gt;It can use that information before opening a directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling a query instead of filtering a walk
&lt;/h2&gt;

&lt;p&gt;Branchcut parses each pattern into path components. A pattern such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packages/core/src/**/[a-z]*.{rs,ts}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes a compact representation containing literal components, a path-level globstar, character ranges, a wildcard segment, and expanded brace alternatives.&lt;/p&gt;

&lt;p&gt;The planner then looks for useful structure.&lt;/p&gt;

&lt;p&gt;For a fixed-prefix pattern such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packages/core/src/**/*.rs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the traversal begins at &lt;code&gt;packages/core/src&lt;/code&gt;. It does not open the repository root and rediscover that prefix one entry at a time.&lt;/p&gt;

&lt;p&gt;When several patterns share a prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/**/*.rs
src/**/*.toml
src/**/test*.rs
src/components/**/*.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;they are compiled into a shared trie/NFA-style program. A directory entry advances the active shared states once rather than being converted into a full string and independently tested against every pattern.&lt;/p&gt;

&lt;p&gt;Each traversal frame carries the positive and exclusion states that are still alive at that location. Before descending into a directory, Branchcut asks two questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can any positive state still match a descendant here?
Does an exclusion safely cover this entire subtree?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the first answer is no, or the second answer is yes, the directory is pruned. No &lt;code&gt;read_dir&lt;/code&gt;, no entries, and no paths to throw away later.&lt;/p&gt;

&lt;p&gt;This is why the name is Branchcut. The useful optimization is not walking every branch a few percent faster. It is cutting branches that cannot produce an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  One executable instead of a toolchain made of packages
&lt;/h2&gt;

&lt;p&gt;Globbing alone would not have solved the original problem. I wanted something useful from the terminal without immediately wrapping it in another script.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9n770a1it5eu86dkf089.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9n770a1it5eu86dkf089.png" alt="Globbing, walking, ignore handling, filtering, streaming, command execution, explain output, and traversal statistics converge into one Branchcut executable built from one Rust source file." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Globbing alone would not have solved the original problem. I wanted something useful from the terminal without immediately wrapping it in another script.&lt;/p&gt;

&lt;p&gt;Branchcut pulls the jobs usually spread across a small package stack into one executable: globbing, walking, ignore handling, filtering, streaming, shell-free execution, plan explanation, and traversal statistics.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One query compiler, one traversal engine, one self-contained binary.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Those features would normally suggest several crates: &lt;code&gt;clap&lt;/code&gt;, &lt;code&gt;globset&lt;/code&gt;, &lt;code&gt;walkdir&lt;/code&gt;, &lt;code&gt;ignore&lt;/code&gt;, &lt;code&gt;regex&lt;/code&gt;, &lt;code&gt;rayon&lt;/code&gt;, &lt;code&gt;serde_json&lt;/code&gt;, and a command helper. Branchcut does not include them. The complete substitution ledger is in &lt;a href="https://github.com/codex-mohan/branchcut/blob/master/STDLIB.md" rel="noopener noreferrer"&gt;&lt;code&gt;STDLIB.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That does not mean the standard library made everything easy. It means the difficult parts were visible instead of delegated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts that fought back during development
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffwidwl4f4f4tsaxekmaq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffwidwl4f4f4tsaxekmaq.png" alt="Globstar correctness, ignore re-inclusion, output semantics, and parallel ordering converge on one rule: correctness before speed." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first deceptively small problem was &lt;code&gt;**&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At the path-component level, globstar means zero or more components. Therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/**/mod.rs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;must match both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/mod.rs
src/a/b/mod.rs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Treating it as an ordinary greedy string wildcard breaks the zero-directory case. In the compiled program it needs both a transition that stays on the globstar while consuming a component and an epsilon transition that moves forward without consuming one.&lt;/p&gt;

&lt;p&gt;That was only the beginning. The same state has to remain useful while deciding whether a directory can still produce a match. A matcher that can answer “does this path match?” is not automatically a planner that can answer “is it safe to avoid opening this subtree?” Multiple patterns made that more interesting: shared prefixes needed shared traversal state, which meant restructuring the walker so each directory frame could carry the still-possible positive and exclusion states forward without repeatedly rebuilding full path strings or testing every pattern from scratch.&lt;/p&gt;

&lt;p&gt;Ignore rules were harder in a different way. Given:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generated/
!generated/keep.rs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pruning &lt;code&gt;generated/&lt;/code&gt; immediately would be fast and wrong. A later negation can re-include a descendant, so Branchcut keeps the subtree open when re-inclusion is possible. I would rather open one extra directory than publish a pruning counter achieved by losing valid results.&lt;/p&gt;

&lt;p&gt;The bugs were not all exotic matcher failures. Some were ordinary command-line semantics that become very visible in a tool people put into scripts. A simple positional search such as &lt;code&gt;branchcut config&lt;/code&gt; needed to remain a literal filename search, not quietly become a glob expression. Hidden paths and file-type filters had to preserve their meaning after prefix planning narrowed the traversal root. Count-only output still had to honor &lt;code&gt;--limit&lt;/code&gt;. Sorting had to change the meaning of a limit: a streaming limit can stop traversal immediately, while a globally sorted limit must inspect and collect every match first.&lt;/p&gt;

&lt;p&gt;Even writing output required care. A closed pipe should not turn a useful command into a panic just because its consumer stopped reading. Unix filenames are byte sequences, not guaranteed UTF-8 strings, so the matcher works from &lt;code&gt;OsStr&lt;/code&gt; bytes there instead of converting every name through &lt;code&gt;to_string_lossy()&lt;/code&gt;. Windows has different string semantics, so the compatibility document states the current lossy boundary rather than hiding it.&lt;/p&gt;

&lt;p&gt;Parallel traversal arrived only after the sequential engine was correct. A bounded worker pool needed bounded per-worker queues, work stealing, outstanding-task tracking, condition-variable sleeping, atomic cancellation, reusable worker-local buffers, and one coordinator responsible for buffered output and errors. It also changed what the CLI could honestly promise: Branchcut rejects &lt;code&gt;--threads&lt;/code&gt; with &lt;code&gt;--limit&lt;/code&gt; or &lt;code&gt;--exec&lt;/code&gt;, because those options require exact global early-stop or execution ordering.&lt;/p&gt;

&lt;p&gt;Zero dependencies meant owning the unglamorous code too: argument parsing with &lt;code&gt;std::env::args_os&lt;/code&gt;, contextual errors without &lt;code&gt;anyhow&lt;/code&gt;, JSON Lines without &lt;code&gt;serde_json&lt;/code&gt;, hierarchical ignore parsing without &lt;code&gt;ignore&lt;/code&gt;, and shell-free execution through &lt;code&gt;std::process::Command&lt;/code&gt;. The one-source-file rule made feature cuts necessary. Branchcut does not claim extglobs, nested braces, metadata predicates, every Git ignore escape rule, full &lt;code&gt;fast-glob&lt;/code&gt; API compatibility, or a watch mode. Cutting an unsupported feature is better than shipping a convenient lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the planner observable
&lt;/h2&gt;

&lt;p&gt;Optimization claims are easy when the work is invisible. I wanted Branchcut to show its reasoning.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--explain&lt;/code&gt; prints decisions before traversal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QUERY PLAN

ROOT
  packages

SHARED LITERAL PREFIX
  packages

POSITIVE PATTERNS
  packages/**/src/**/*.rs [FixedPrefixRecursive]
  packages/**/src/**/*.ts [FixedPrefixRecursive]

EXCLUSIONS
  **/target/** [UnboundedRecursive]
  **/node_modules/** [UnboundedRecursive]

METADATA
  not required

TERMINATION
  first 100 matches
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--stats&lt;/code&gt; reports what happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;matched                 10000
directories considered   101
directories opened         81
directories pruned         20
entries inspected       13100
candidate files         13000
metadata calls              1
filesystem errors           0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metadata count matters. If a query only needs entry names and file types, Branchcut uses &lt;code&gt;DirEntry::file_type()&lt;/code&gt; and avoids a separate &lt;code&gt;metadata()&lt;/code&gt; call for every candidate. The root is inspected once with &lt;code&gt;symlink_metadata&lt;/code&gt;; the current filters do not need per-entry metadata.&lt;/p&gt;

&lt;p&gt;The counters turned the planner from an architectural claim into something I could test. A performance improvement was only interesting if the result set remained correct and the counters explained where the time went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then benchmarking became the painful part
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhx27yuqrjozq1102b6up.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhx27yuqrjozq1102b6up.png" alt="Node, native, and Zig implementations must produce the same result set before cold CLI and hot-engine timings mean anything." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I expected the glob parser to consume most of the time. It did not. The most frustrating part of the project was trying to produce a benchmark I could actually believe.&lt;/p&gt;

&lt;p&gt;The tools do not naturally run under the same conditions. &lt;code&gt;fast-glob&lt;/code&gt; and &lt;code&gt;tinyglobby&lt;/code&gt; are Node packages whose normal APIs return arrays. Branchcut is a native executable that streams by default. zlob is a native Zig project with a CLI, a public matching API, and other walker paths with different capabilities. A single stopwatch number can silently mix process startup, module loading, traversal, sorting, output capture, and entirely different result sets.&lt;/p&gt;

&lt;p&gt;My first rule became embarrassingly simple: count the results before trusting the time. For every comparable workload, I normalized separators, converted outputs into sets, and checked both directions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Branchcut - competitor
competitor - Branchcut
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both differences had to be empty. Matching counts alone were not enough; two wrong sets can have the same size. A tool that finishes instantly because a Windows path was interpreted differently has not won a benchmark. It has answered a different query.&lt;/p&gt;

&lt;p&gt;This caught real inconsistencies. The zlob CLI I tested returned the expected result for a broad &lt;code&gt;**/*.rs&lt;/code&gt; query, but its nested-globstar results on Windows did not agree with Branchcut, &lt;code&gt;fast-glob&lt;/code&gt;, or &lt;code&gt;tinyglobby&lt;/code&gt;. Another official benchmark path returned zero matches when given a Windows drive-letter path. Those runs were useless as performance evidence, so I retained the mismatches in the comparison notes and excluded the invalid timings instead of quietly presenting a spectacular zero-work victory.&lt;/p&gt;

&lt;p&gt;Startup needed its own category. Launching Node, loading a module, executing a query, sorting, and capturing output is a legitimate measurement for a command-line user. It is not a clean engine comparison. I therefore kept two categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cold CLI:&lt;/strong&gt; launch a fresh process and include startup, loading, traversal, sorting, and output capture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot engine:&lt;/strong&gt; load the Node modules once, warm them up, consume results without printing, and time repeated queries inside the same process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Branchcut needed the same care. Its &lt;code&gt;--stats&lt;/code&gt; timer begins after argument parsing and planning, so the hot comparison uses count-only output and the internal elapsed value. For zlob, I built a temporary Zig harness around its public filesystem API so repeated queries could run inside one process. That harness was development equipment, not a dependency shipped with Branchcut.&lt;/p&gt;

&lt;p&gt;Even after the harnesses agreed, filesystem benchmarks moved around. The first run could be dominated by cold caches. Antivirus activity and unrelated machine load appeared as outliers. Printing 13,000 paths could cost more than matching them. Sorting one side but not the other could reverse a result. I used warmups, repeated samples, medians, P90 where available, identical corpora, and identical result requirements. I retained the slow runs and the cases Branchcut could not fairly claim.&lt;/p&gt;

&lt;p&gt;That process was slower and less satisfying than writing an optimization. It was also more valuable. The final table is modest compared with the number of ways I found to produce a misleading table.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the measurements showed
&lt;/h2&gt;

&lt;p&gt;With those rules in place, I used a generated 16,000-file corpus shaped like a small monorepo: 20 packages containing source files, &lt;code&gt;target&lt;/code&gt; output, and &lt;code&gt;node_modules&lt;/code&gt; content. For the supported cases in the published comparison, Branchcut, &lt;code&gt;fast-glob&lt;/code&gt;, and &lt;code&gt;tinyglobby&lt;/code&gt; returned equal sets.&lt;/p&gt;

&lt;p&gt;The hot query was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;**/*.{rs,toml}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returned 13,000 matches with hidden files excluded and no path serialization. Node packages were loaded once and warmed up. Branchcut used its internal elapsed statistic after argument parsing and planning.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Time per query&lt;/th&gt;
&lt;th&gt;Relative to Branchcut&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Branchcut&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22.079 ms median&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.00×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tinyglobby 0.2.14&lt;/td&gt;
&lt;td&gt;35.666 ms median&lt;/td&gt;
&lt;td&gt;1.61× slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fast-glob 3.3.3&lt;/td&gt;
&lt;td&gt;37.528 ms median&lt;/td&gt;
&lt;td&gt;1.70× slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;zlob 1.6.3 public &lt;code&gt;match&lt;/code&gt; API&lt;/td&gt;
&lt;td&gt;133.408 ms average&lt;/td&gt;
&lt;td&gt;6.04× slower&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The zlob number needs context. It came from its direct, single-threaded public &lt;code&gt;match&lt;/code&gt; API on Windows and does not represent every optimized walker path zlob offers on every platform.&lt;/p&gt;

&lt;p&gt;I measured cold command invocation separately. For an exclusion-heavy query returning the same 10,000 sorted paths, Branchcut's median was 24.99 ms and &lt;code&gt;fast-glob&lt;/code&gt;'s was 148.53 ms. That 5.94× ratio includes Node startup and module loading, so I do not present it as an engine-only comparison.&lt;/p&gt;

&lt;p&gt;The complete environment, workload, warmups, result counts, caveats, and even the awkward competitor behavior are recorded in &lt;a href="https://github.com/codex-mohan/branchcut/blob/master/COMPARISON.md" rel="noopener noreferrer"&gt;&lt;code&gt;COMPARISON.md&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/codex-mohan/branchcut/blob/master/BENCHMARKS.md" rel="noopener noreferrer"&gt;&lt;code&gt;BENCHMARKS.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One limitation also deserves to stay visible: two clean Windows release builds did not produce byte-identical hashes. I suspect linker metadata, but suspicion is not evidence. Branchcut therefore does not claim the hackathon's reproducible-build bonus.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Branchcut compares with fd and ripgrep
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;fast-glob&lt;/code&gt; was the Package Killer target, but it was not the only comparison worth making. Developers often reach for &lt;a href="https://github.com/sharkdp/fd" rel="noopener noreferrer"&gt;&lt;code&gt;fd&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://github.com/BurntSushi/ripgrep" rel="noopener noreferrer"&gt;&lt;code&gt;ripgrep&lt;/code&gt;&lt;/a&gt; when they need to locate files from a terminal. They are mature native tools with excellent defaults, so this was not an attempt to manufacture an easy win.&lt;/p&gt;

&lt;p&gt;I benchmarked &lt;code&gt;branchcut.exe&lt;/code&gt; directly against fd 10.4.2 and ripgrep 15.1.0 on a synthetic Windows tree containing 16,247 files and 1,057 directories. These are observed end-to-end wall-clock timings for comparable file-finding workloads, including fresh process startup and PowerShell invocation. They are not pure traversal timings or universal claims.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Branchcut&lt;/th&gt;
&lt;th&gt;fd&lt;/th&gt;
&lt;th&gt;ripgrep&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All files&lt;/td&gt;
&lt;td&gt;~176 ms&lt;/td&gt;
&lt;td&gt;~276 ms&lt;/td&gt;
&lt;td&gt;~215 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;*.ts&lt;/code&gt; files&lt;/td&gt;
&lt;td&gt;~142 ms&lt;/td&gt;
&lt;td&gt;~142 ms&lt;/td&gt;
&lt;td&gt;~154 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First matching file&lt;/td&gt;
&lt;td&gt;~21 ms&lt;/td&gt;
&lt;td&gt;~34 ms&lt;/td&gt;
&lt;td&gt;~31 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branchcut internal traversal, 1 thread&lt;/td&gt;
&lt;td&gt;81 ms&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branchcut internal traversal, 8 threads&lt;/td&gt;
&lt;td&gt;20 ms&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The wall-clock and internal numbers answer different questions. For the TypeScript workload, Branchcut's &lt;code&gt;--stats&lt;/code&gt; reported about 86 ms after argument parsing and planning, while the eight-thread internal run was about 20 ms. The larger end-to-end number includes the surrounding Windows and PowerShell launch cost. That distinction matters; calling either number “the traversal time” would be misleading.&lt;/p&gt;

&lt;p&gt;The more useful comparison is capability shape:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Branchcut&lt;/th&gt;
&lt;th&gt;fd&lt;/th&gt;
&lt;th&gt;ripgrep&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Glob matching&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extension filtering&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;Via glob/filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File, directory, and symlink filtering&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Primarily files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hidden files&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--hidden&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--hidden&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--hidden&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exclusion patterns&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prunes excluded subtrees&lt;/td&gt;
&lt;td&gt;Yes, explicitly&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First or limited results&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--first&lt;/code&gt;, &lt;code&gt;--limit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--max-results&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shell or pipeline workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deterministic sorting&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--sort&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;External tool or shell&lt;/td&gt;
&lt;td&gt;External tool or shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.gitignore&lt;/code&gt; support&lt;/td&gt;
&lt;td&gt;Opt-in &lt;code&gt;--gitignore&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Native default&lt;/td&gt;
&lt;td&gt;Native default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File-result JSON Lines&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;--json&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No direct equivalent&lt;/td&gt;
&lt;td&gt;No direct equivalent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Command execution&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;--exec&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;--exec&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No direct equivalent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain query plan&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;--explain&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Traversal statistics&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;--stats&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strict filesystem errors&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;--strict&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallel traversal control&lt;/td&gt;
&lt;td&gt;Explicit &lt;code&gt;--threads N&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Internal&lt;/td&gt;
&lt;td&gt;Internal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the difference I care about. Branchcut is not merely trying to be a faster filename lister. fd is still an excellent default for a quick interactive find, and ripgrep remains the right tool when the primary question is about file content. Branchcut becomes interesting when the query itself needs to be inspected, combined, limited, streamed, explained, and made accountable for the filesystem work it performed.&lt;/p&gt;

&lt;p&gt;That makes it closer to a programmable filesystem query engine than a basic glob utility: positive and negative globs become one plan, exclusions become pruning decisions, limits become cancellation, and &lt;code&gt;--stats&lt;/code&gt; and &lt;code&gt;--explain&lt;/code&gt; expose the result instead of asking users to trust a black box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correctness had to come before speed
&lt;/h2&gt;

&lt;p&gt;The project has no external Rust test framework. Its tests live at the bottom of the same source file using &lt;code&gt;#[test]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;They cover the basic matcher syntax, zero-component globstars, braces, common-prefix planning, shared compilation, hidden paths, extension filters, exclusions, early termination, sorted limits, nested ignore rules, broken pipes, deep trees, symlink policy, parallel/sequential equality, and non-UTF-8 Unix names.&lt;/p&gt;

&lt;p&gt;Whenever an optimization changed traversal, the relevant result comparison came first. A fast filesystem query engine that occasionally omits a path is simply a bug with an impressive benchmark.&lt;/p&gt;

&lt;p&gt;The same rule influenced features I did not add. Branchcut does not support extglobs, nested braces, metadata predicates, or every &lt;code&gt;.gitignore&lt;/code&gt; escape rule. It is not a drop-in replacement for the entire &lt;code&gt;fast-glob&lt;/code&gt; API. Those limitations are written down in &lt;a href="https://github.com/codex-mohan/branchcut/blob/master/COMPATIBILITY.md" rel="noopener noreferrer"&gt;&lt;code&gt;COMPATIBILITY.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I would rather have a smaller language with testable semantics than a long feature list made of optimistic claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing it
&lt;/h2&gt;

&lt;p&gt;Branchcut can be installed directly from GitHub with Cargo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--locked&lt;/span&gt; &lt;span class="nt"&gt;--git&lt;/span&gt; https://github.com/codex-mohan/branchcut.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a simple filename search is just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;branchcut config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An explicit query can combine several concerns without another wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;branchcut &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--glob&lt;/span&gt; &lt;span class="s1"&gt;'src/**/*.{rs,toml}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude&lt;/span&gt; &lt;span class="s1"&gt;'**/target/**'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; file &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--limit&lt;/span&gt; 20 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stats&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the installation can be removed cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo uninstall branchcut
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository also contains dedicated PowerShell and POSIX installers plus matching uninstallers. A CI matrix exercises the install, query, and uninstall lifecycle on Windows, Ubuntu, and macOS.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took away from the constraint
&lt;/h2&gt;

&lt;p&gt;Zero dependencies did not automatically make the program fast. Rewriting a generic walker badly would only have produced a dependency-free slow program.&lt;/p&gt;

&lt;p&gt;The useful part of the constraint was that it forced the layers into the same room. The parser could describe the query in a form the traversal understood. Exclusions could become pruning decisions. A limit could become cancellation. Type information could prevent metadata calls. Shared prefixes could become one starting directory instead of repeated matches against unrelated paths.&lt;/p&gt;

&lt;p&gt;That is the part of Branchcut I want to keep developing.&lt;/p&gt;

&lt;p&gt;There will always be cases where &lt;code&gt;fast-glob&lt;/code&gt;, &lt;code&gt;zlob&lt;/code&gt;, &lt;code&gt;fd&lt;/code&gt;, or another mature tool is the better choice. Branchcut's argument is more specific: if a filesystem query contains enough information to prove that a subtree is irrelevant, the engine should use that information before it pays to open the subtree.&lt;/p&gt;

&lt;p&gt;Compile the query. Cut the tree.&lt;/p&gt;




&lt;p&gt;Branchcut was built for the &lt;a href="https://zerodepshack.com/" rel="noopener noreferrer"&gt;Zero Dependency 72-Hour Hackathon&lt;/a&gt; by Hackathon Raptors, also to make good FOSS project that everyone can use :)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/codex-mohan/branchcut" rel="noopener noreferrer"&gt;Source code and README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/codex-mohan/branchcut/blob/master/STDLIB.md" rel="noopener noreferrer"&gt;Standard-library substitution ledger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/codex-mohan/branchcut/blob/master/COMPARISON.md" rel="noopener noreferrer"&gt;Benchmark methodology and results&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/codex-mohan/branchcut/blob/master/COMPATIBILITY.md" rel="noopener noreferrer"&gt;Compatibility boundaries&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productivity</category>
      <category>automation</category>
      <category>cli</category>
      <category>filesystem</category>
    </item>
  </channel>
</rss>
