ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 15

  1. Blog

micromatch vs picomatch: 3.7 ms vs 1.8 ms on 7,800 Paths

Aditya Challa·5 October 2026·7 min read

Hands-on
On this page
  1. What I tested
  2. Is picomatch faster than micromatch?
  3. What does each one cost to install and bundle?
  4. Is the braces advisory a real problem?
  5. Do they match the same paths?
  6. Which one should you pick?
  7. Sources
  8. Related

I filtered the same 7,800 repo paths with both libraries on this box: micromatch 4.0.8 took a median 3.73 ms to return the 481 .js files outside node_modules from an include plus a negation, while two picomatch 4.0.7 matchers inside Array.filter returned the same 481 in 1.81 ms. micromatch also installs 6 packages against picomatch's 1, and npm audit flags its braces dependency as high severity with no patched version listed.

Short answer: use picomatch directly when you control the patterns and want the smallest, browser-safe matcher, and keep micromatch when you need its list helpers or real brace expansion. Either way, compile each pattern once. No affiliate links in this post.

What I tested

  • Machine: 8 vCPU Intel Xeon / 15 GB RAM shared Linux cloud box, tested 6 Oct 2026 about 03:30 IST.
  • Versions: micromatch 4.0.8 and picomatch 4.0.7 (npm latest that day), Node 20.19.2, esbuild 0.28.2 for bundle sizes. Note that micromatch 4.0.8 does not use picomatch 4; its lockfile resolved picomatch 2.3.2 plus braces 3.0.3, fill-range, to-regex-range and is-number.
  • Input: 7,800 relative file paths listed from a copied Jest/Vitest project, node_modules included. Patterns, in words: the recursive any-depth .js pattern; a src-only pattern with a js/ts/mjs brace list; an extglob for test or spec files; a numeric range from 1 to 20; and an any-depth node_modules negation.
  • Timing: each job warmed once, then the median of 21 runs (11 for the compile-per-call jobs), in 6 fresh processes with the library order alternated. Results are the median of those 6. Scripts and JSON are in /workspace/bench-micromatch-picomatch/.
  • Not tested: Windows paths and the windows option, real file-system globbing (that is fast-glob's job, which uses micromatch), Bun or Deno, and micromatch's own newer major if one ships.

Is picomatch faster than micromatch?

Job on 7,800 paths (median, ms)micromatch 4.0.8picomatch 4.0.7Matches
Precompiled matcher, recursive .js1.3221.3173,736
Precompiled matcher, src brace list0.4000.415440
Precompiled matcher, test/spec extglob1.3951.41353
Filter with include + node_modules negation3.732 (3.50–4.58)1.812 (1.76–1.96)481

Once a pattern is compiled, the two are the same speed, which makes sense because micromatch's matcher is a picomatch regex underneath. The 2x gap is in the list call: micromatch(list, patterns) handles the negation and de-duplication for you, and that bookkeeping costs about 1.9 ms on this input. Writing the two matchers yourself and filtering is a three-line change.

The bigger cost is compiling inside a loop. Calling isMatch with a pattern string 1,000 times took 16.3 ms for the extglob with micromatch and 15.7 ms with picomatch, about 16 µs a call, while the precompiled matcher handled 7,800 paths in about 1.4 ms, roughly 0.18 µs a path. That is close to 90x, and it is the same for both libraries. It is the same compile-once lesson as lru_cache hit vs miss, and Python users will recognize the shape from fnmatch vs re.

What does each one cost to install and bundle?

PackagePackagesnode_modulesesbuild node (min / gzip)Browser build
micromatch 4.0.86360 KB41,479 B / 14,756 B, 18 inputsfailed: needs util and path
picomatch 4.0.71132 KB24,518 B / 8,892 B, 7 inputsbuilt, same size

The browser failure is the kind of thing pathe vs node:path is about: Node built-ins do not resolve in a browser bundle. If you ship a matcher to a web tool or an editor extension, picomatch avoids that, and you can hold the 5.9 KB gzip difference in place with size-limit vs bundlesize.

Is the braces advisory a real problem?

npm audit on a fresh micromatch install reported 2 high-severity findings, both from GHSA-vfj7-8cjw-p6xm: braces through 3.0.3 can overflow the call stack on deeply nested brace patterns, and the advisory lists no patched version. A fresh picomatch install reported 0. I checked what that means on this box with nested patterns of the form opening braces, a comma list, closing braces, each in a fresh process:

Call (fresh process)6,003 chars8,003 chars9,983 chars
micromatch.braces()okRangeErrorRangeError
micromatch.braces() with expandokokRangeError
micromatch.isMatch()okokok
micromatch(list, pattern)okokok
picomatch.isMatch()okokok

braces rejects input over 10,000 characters with a SyntaxError, so the overflow window is just under that cap. In my runs it only fired through micromatch.braces(); the matching calls returned normally on the same strings. Separately, a 40,001-character nested pattern passed to isMatch crashed both libraries with a V8 regex out-of-memory abort (exit code 134), which no try/catch can stop. If patterns come from users, cap their length yourself. The broader habit of checking what a dependency pulls in is in the supply-chain checklist for small teams, and ncu vs npm outdated is how I watch for a patched braces release.

Do they match the same paths?

Pattern and inputmicromatchpicomatch
file-{1..3}.js vs file-2.jstruetrue
f-{1..20}.js vs f-5.js and f-15.jsfalse, falsefalse, false
f-{1..20}.js vs f-0.jstruetrue
f-{1..20}.js with expandRange via fill-range, vs f-15.jstruetrue
a/{b,c}/d.js vs a/c/d.jstruetrue
star vs .env, default / dot: truefalse / truefalse / true
src backslash a.js vs src/star.js on Linuxfalsefalse
List helpers (not, some, every, contains, matchKeys, braces)yesno; returns a matcher, plus scan, parse, makeRe, test

The surprise row: both libraries compile a multi-digit range like 1..20 to the character class 1-20, which matches only 0, 1 and 2. f-15.js and f-5.js silently fail, and the zero-padded 01..10 form failed too. Passing an expandRange function built on fill-range fixed it in both, as did expanding the list first with micromatch.braces and the expand option. If unused pattern lists are piling up in a repo, Knip vs depcheck finds the dead files they were written for.

Which one should you pick?

SituationMy pick
Hot path, patterns known up frontpicomatch, compiled once
Browser or editor-extension bundlepicomatch
Need not, some, every or matchKeys on listsmicromatch
Need real numeric range expansionmicromatch.braces with expand, or picomatch with expandRange
User-supplied patternseither, with your own length cap

Bottom line: on this box micromatch 4.0.8 filtered 7,800 paths in 3.73 ms against 1.81 ms for picomatch 4.0.7, at 14,756 B versus 8,892 B gzip and 6 packages versus 1. Precompiled matchers were the same speed, and compiling per call was about 90x slower in both. Who should not switch to picomatch: code that leans on micromatch's list helpers or brace expansion. Who should not stay on micromatch: browser bundles and hot paths that only need a yes/no match.

How this was made: I installed both packages on the ShopperCove box, listed a real test project's paths as input, timed matchers, per-call compiles and list filtering in 6 fresh processes with the order alternated, bundled each package with esbuild, ran npm audit, and tested range, dotfile and nested-brace behavior in separate processes. The JSON results sit next to the scripts. The write-up was drafted with AI help and checked against that output.

Sources

  • https://github.com/micromatch/micromatch
  • https://github.com/micromatch/picomatch
  • https://github.com/micromatch/braces
  • https://github.com/advisories/GHSA-vfj7-8cjw-p6xm
  • https://github.com/jonschlinkert/fill-range
  • https://www.npmjs.com/package/micromatch
  • https://www.npmjs.com/package/picomatch
  • https://esbuild.github.io/

Related

  • https://www.shoppercove.com/blog/lru-cache-hit-miss-localhost-lab
  • https://www.shoppercove.com/blog/fnmatch-vs-re-localhost-lab
  • https://www.shoppercove.com/blog/pathe-vs-node-path
  • https://www.shoppercove.com/blog/size-limit-vs-bundlesize
  • https://www.shoppercove.com/blog/after-xz-practical-supply-chain-checklist-solo
  • https://www.shoppercove.com/blog/ncu-vs-npm-outdated
  • https://www.shoppercove.com/blog/knip-vs-depcheck
  • https://www.shoppercove.com/blog/path-glob-vs-fnmatch-localhost-lab
micromatchpicomatchnode.jsglobperformancebenchmarkingdependency audit

Lab evidence

What I found running this

Hands-on on ShopperCove box 6 Oct 2026 ~03:30 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 20.19.2). micromatch 4.0.8 (resolves picomatch 2.3.2 + braces 3.0.3 + fill-range + to-regex-range + is-number) vs picomatch 4.0.7; esbuild 0.28.2. Input: 7,800 relative paths from a copied Jest/Vitest project. Median of 21 runs (11 for per-call compile) x 6 fresh processes, order alternated (ms on 7,800 paths): precompiled recursive .js 1.322 vs 1.317 (3,736 hits), src brace list 0.400 vs 0.415 (440), test/spec extglob 1.395 vs 1.413 (53); micromatch(list, [include, negation]) 3.732 (3.50-4.58) vs two picomatch matchers + Array.filter 1.812 (1.76-1.96), 481 hits each. isMatch with pattern string x1,000: extglob 16.3 vs 15.7 ms (~90x precompiled). Bundle node 41,479/14,756 B (18 inputs) vs 24,518/8,892 B (7); micromatch browser build fails on util/path. Install 6 pkgs 360 KB vs 1 pkg 132 KB; npm audit 2 high (GHSA-vfj7-8cjw-p6xm, braces <=3.0.3, no patch) vs 0. Fresh-process nested braces: micromatch.braces RangeError at 8,003 chars, expand at 9,983; isMatch/list/picomatch ok to 9,983; >10,000 chars SyntaxError; 40,001-char pattern into isMatch aborted both (V8 regex OOM, exit 134). Range {1..20} compiles to [1-20] in both (f-15.js false) unless expandRange/fill-range. Not tested: Windows paths/windows option, fs globbing, Bun/Deno. No affiliate.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Related links

  • Plate 08

    fast-glob vs globby: 0.7 ms vs 14 ms With gitignore

    5 Oct 2026

  • Plate 55

    Klona vs structuredClone: 1.4M vs 225k Clones per Second

    5 Oct 2026

  • Plate 12

    Yarn Berry vs pnpm: 1.3s vs 0.24s Install

    Yarn 4.9.2 (Berry PnP + node-modules linker) vs pnpm 12.9.1 on one 22-dependency React+Vite app. Isolated caches (Yarn enableGlobalCache false; pnpm --store-dir). Warm medians (5): Yarn PnP 1.307 s, Yarn nm 2.471 s, pnpm 0.240 s. Lock+empty medians 1.656 / 2.789 / 0.725 s. Clean disk: Yarn PnP 246 MB; Yarn nm modules 189 MB; pnpm hardlink project 167 MB. Fixture /workspace/bench-yarn-pnpm/. Distinct from pnpm-vs-npm-vs-bun (no Yarn).

    5 Oct 2026

On this page

  1. What I tested
  2. Is picomatch faster than micromatch?
  3. What does each one cost to install and bundle?
  4. Is the braces advisory a real problem?
  5. Do they match the same paths?
  6. Which one should you pick?
  7. Sources
  8. Related
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove