ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 71

  1. Blog

find-up vs locate-path: 47 µs Sync vs 596 µs Async

Aditya Challa·5 October 2026·8 min read

Hands-on
On this page
  1. What I tested
  2. How much does walking up cost?
  3. Is locate-path faster in a single folder?
  4. Do they behave the same?
  5. What do they cost to install and load?
  6. Which one should you pick?
  7. Sources
  8. Related

I timed find-up 8.0.0 against locate-path 8.0.0 finding package.json 12 folders above the working directory on this box: findUpSync took 47 µs, async findUp took 596 µs, and a hand-written loop around async locatePath took 571 µs (Node 20, median, run 1). The sync API was 12.6× to 13.7× faster than the async one across both runs, and sync won every case I tried (6× to 14×).

Short answer: they are not rivals so much as two layers. find-up calls locate-path once per folder on its way up, so pick locate-path when you only check one folder and find-up when you need to walk up, and in both cases reach for the sync function unless you are checking a slow or network disk. 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 04:15 to 04:30 IST.
  • Versions: find-up 8.0.0 and locate-path 8.0.0 (both npm latest that day; find-up 8 depends on locate-path ^8.0.0), Node 20.19.2, esbuild 0.28.2 for bundle sizes.
  • Workload: four cases. One folder with 8 candidate config names where only the last one exists; package.json 12 folders up; 8 config names checked in each of 12 folders up to a .myapprc.json at the top; and a miss, walking 12 folders up to a stopAt folder without finding anything. As a baseline I also timed a hand-rolled loop that calls fs.statSync inside try / catch.
  • Timing: median of 9 rounds of 2,000 calls after 200 warm-up calls, two processes with the run order flipped, reported in microseconds per call. Scripts and JSON in /workspace/bench-find-up-locate-path/.
  • Also measured: cold import plus first call (11 fresh processes), esbuild bundle size, fresh install size, and behavior checks for directories, symlinks, matcher functions, preserveOrder, findUpMultiple and findDown.
  • Not tested: network or Docker bind-mounted disks (where async may help), Windows or macOS, Bun and Deno, path-exists or other find-up alternatives such as empathic.

How much does walking up cost?

Node 20.19.2, median µs per call (run 1 / run 2):

CasefindUpfindUpSynclocatePath loop (async)hand-rolled statSync loop
package.json, 12 folders up595.63 / 613.6347.19 / 44.85570.66 / 546.42154.98 / 162.02
8 names per folder, 12 folders up1,874.72 / 1,834.29269.37 / 284.681,738.92 / 1,803.621,301.32 / 1,361.42
miss, 12 folders to stopAt563.63 / 573.8141.41 / 43.99not run161.29 / 167.03

find-up's own overhead on top of locate-path was small: 596 to 614 µs vs 546 to 571 µs for the same walk written by hand, so roughly 4% to 12% (2% to 8% in the 8-name case). The big number is async vs sync. Every async check is a separate fs.promises.stat call plus promise scheduling, while the sync path runs a plain statSync with throwIfNoEntry: false. My own try / catch loop was 3× slower than findUpSync because throwing and catching an ENOENT error for each missing file costs more than the stat itself. If you then glob from the folder you found, the next costs are in fast-glob vs globby and micromatch vs picomatch.

Is locate-path faster in a single folder?

One folder, 8 candidate names, match on the last name. Median µs per call (run 1 / run 2):

Callµs
locatePathSync15.02 / 13.82
findUpSync with stopAt set to the same folder19.86 / 20.09
hand-rolled statSync try / catch77.89 / 75.87
locatePath (default: concurrency Infinity, preserveOrder true)138.56 / 135.92
findUp with stopAt set to the same folder152.53 / 139.77
locatePath, preserveOrder: false184.88 / 132.14
locatePath, concurrency: 1269.08 / 266.57

In one folder, locate-path saved about 5 to 6 µs sync and 4 to 14 µs async over calling find-up with stopAt. preserveOrder: false, which the docs suggest for speed, gave no reliable gain here: one run slower, one run 3% faster. concurrency: 1 doubled the time, because the 8 stats then ran one after another. Path handling in these loops is node:path; if you need the same code on Windows-style paths, see pathe vs node:path.

Do they behave the same?

CheckResult
locatePath(['package.json']) from 12 folders downundefined; it never walks up
findUp('package.json') with a second package.json 6 folders upreturned the nearer one
findUpMultiple('package.json', stopAt)both files, nearest first
findUp('.git') where .git is a folderundefined; default type is 'file'
findUp('.git', { type: 'directory' })found
Matcher function returning findUpStop at folder l6undefined; stopped early
findDown('package.json', depth 10) from the topfound the top-level file first
locatePath with 3 existing files, preserveOrder: false, 200 callsfirst name 188 times, second name 12 times
Symlinked file, default optionsmatched
Symlinked file, allowSymlinks: falseundefined (both packages)
cwd passed as a file URLworked in both
import { pathExists } from 'find-up'SyntaxError; not exported in v8

Two of these are easy to trip over. The .git case returns nothing unless you pass type: 'directory', which is how repo-root finders quietly fail. And preserveOrder: false really does change the answer when more than one candidate exists, so only use it when any match will do. Package-manager detection is a common use for this exact walk (lockfiles up the tree); I compared two of those in pm-detector vs which-pm. Config loaders do the same walk; c12 and friends sit on top of it, and confbox vs dotenv covers the parsing step after the file is found.

What do they cost to install and load?

Measurefind-up 8.0.0locate-path 8.0.0
esbuild bundle, minified3,512 B2,379 B
Bundle gzip -91,514 B1,160 B
Fresh npm install176 KB, 6 packages104 KB, 4 packages
Cold import + first async call (median of 11)35.77 ms28.58 ms
Cold import + first sync call (median of 11)34.26 ms26.07 ms

Both are tiny. find-up adds unicorn-magic on top of locate-path, p-locate, p-limit and yocto-queue. The cold-start medians were noisy on this shared box (single runs from 25 to 52 ms) and mostly measure Node startup, so I would not choose on them. For a CLI that starts often, the argument parser and process runner usually cost more; see citty vs commander and tinyexec vs execa.

Which one should you pick?

SituationMy pick
Check a few names in one known folderlocatePathSync
Find the nearest package.json, lockfile or config upwardfindUpSync
Find every package.json up to a monorepo rootfindUpMultiple or findUpMultipleSync with stopAt
Find a repo root by its .git folderfindUp with type: 'directory'
Code already async and on a slow or network diskfindUp (not measured here)
Any match will do and you want fewer awaitslocatePath with preserveOrder: false, but check the result order
Fewest dependencieslocate-path, or a statSync loop with throwIfNoEntry: false

Bottom line: on this box find-up 8.0.0 cost about 2% to 12% over a locate-path loop doing the same walk, and the sync versions of both were 6× to 14× faster than the async ones. Who should change code: anyone calling await findUp() during startup on a local disk, where findUpSync returned in 45 to 47 µs instead of 596 to 614 µs. Who can leave it alone: code that runs the walk once per process, where half a millisecond is noise next to a 35 ms Node start. If you also watch the folder you found, see chokidar vs @parcel/watcher.

How this was made: I installed both packages on the ShopperCove box, built a 12-folder fixture, timed four lookup cases over 9 rounds of 2,000 calls in two processes with the order flipped, checked cold start in 11 fresh processes, bundled each entry with esbuild, ran a fresh npm install for size, and ran behavior checks for directories, symlinks, matchers and ordering. 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/sindresorhus/find-up
  • https://www.npmjs.com/package/find-up
  • https://github.com/sindresorhus/locate-path
  • https://www.npmjs.com/package/locate-path
  • https://nodejs.org/api/fs.html#fsstatsyncpath-options

Related

  • https://www.shoppercove.com/blog/fast-glob-vs-globby
  • https://www.shoppercove.com/blog/micromatch-vs-picomatch
  • https://www.shoppercove.com/blog/pathe-vs-node-path
  • https://www.shoppercove.com/blog/pm-detector-vs-which-pm
  • https://www.shoppercove.com/blog/confbox-vs-dotenv
  • https://www.shoppercove.com/blog/citty-vs-commander
  • https://www.shoppercove.com/blog/tinyexec-vs-execa
  • https://www.shoppercove.com/blog/chokidar-vs-parcel-watcher
nodejsperformancebenchmarkingfilesystemsyncasyncfind-uplocate-path

Lab evidence

What I found running this

Hands-on on ShopperCove box 6 Oct 2026 ~04:15-04:30 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 20.19.2). find-up 8.0.0 (depends on locate-path ^8.0.0) vs locate-path 8.0.0; esbuild 0.28.2. 12-folder fixture. Median of 9 x 2,000 calls after 200 warm-up, two processes flipped (µs): pkg 12 up findUp 595.63/613.63, findUpSync 47.19/44.85, locatePath loop 570.66/546.42, statSync try/catch 154.98/162.02; 8 names 1,874.72/1,834.29, 269.37/284.68, 1,738.92/1,803.62, 1,301.32/1,361.42; miss 563.63/573.81, 41.41/43.99, 161.29/167.03; one folder locatePathSync 15.02/13.82, findUpSync 19.86/20.09, locatePath 138.56/135.92, noOrder 184.88/132.14, conc1 269.08/266.57. Behavior: locatePath never walks up; .git needs type directory; preserveOrder false gave 2nd name 12/200; allowSymlinks false rejects symlink; pathExists not exported in v8. Bundle gzip 1,514 vs 1,160 B; install 176 KB/6 vs 104 KB/4; cold 35.77 vs 28.58 ms (noisy). Not tested: network disks, Windows/macOS, Bun/Deno, empathic. No affiliate.

Notes when a lab post goes up

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

Related links

  • Plate 97

    c12 vs cosmiconfig: 0.58 ms vs 1.08 ms per .mjs Load

    5 Oct 2026

  • Plate 79

    pm-detector vs which-pm: 0.49 ms vs 0.043 ms

    5 Oct 2026

  • Plate 93

    mlly vs acorn: 0.046 ms vs 0.017 ms findExports

    5 Oct 2026

On this page

  1. What I tested
  2. How much does walking up cost?
  3. Is locate-path faster in a single folder?
  4. Do they behave the same?
  5. What do they cost to install and load?
  6. Which one should you pick?
  7. Sources
  8. Related
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove