Tools: fd vs find vs ripgrep: I Created 10,000 Files to Settle This Debate (2026)

Tools: fd vs find vs ripgrep: I Created 10,000 Files to Settle This Debate (2026)

fd vs find vs ripgrep: I Created 10,000 Files to Settle This Debate

Why I Did This

Methodology

The Test Bed

Tools Tested

Benchmark 1: Simple Filename Search (Find All *.txt Files)

What I Ran

Results

⚠️ The Hidden Files Gotcha

Benchmark 2: Content Search (Find Files Containing "test")

What I Ran

Results

Benchmark 3: Finding Hidden Files

What I Ran

Results

Benchmark 4: Ignoring .git Directories

What I Ran

Results

But Again: The Counts Don't Match

Bonus Tests

Finding Files by Path Pattern

Simple File Listing Speed

The Scorecard

The Real Talk: When to Use What

Use fd when:

Use find when:

Use rg when:

Use grep when:

The Pipe Combo Pattern (don't do this)

What I Learned

Reproduce This Yourself TL;DR: fd is ~2.5x faster than find for filename searches, rg demolishes grep by ~3x for content searches, and find + grep combined lose on every single benchmark I ran. But there's a catch: both fd and rg skip hidden files by default, which can bite you if you're not paying attention. Here are the receipts. Every time someone posts a shell one-liner using find on Reddit, there's always that guy in the comments: "jUsT uSe fD, iT's fAsTeR." Then someone else chimes in with "actually ripgrep can do that too." I got tired of the anecdotes. I wanted numbers. Real ones. On real files. So I fired up WSL, generated 10,900 files across 1,506 directories (~143 MB of mixed content), and ran actual benchmarks with hyperfine. No synthetic microbenchmarks, no "I feel like X is faster" — just cold, hard terminal output. I created a directory at /tmp/fd-benchmark containing: All benchmarks run with hyperfine --warmup 3 --runs 10 on WSL2 Ubuntu, Intel i7, NVMe SSD. This is the most common use case. You want every .txt file in a project, recursively. Here's something the benchmarks don't tell you — fd and find returned different results. fd skipped 500 .txt files. Why? Because those 500 files live inside .hidden_dir/, and fd ignores hidden directories by default. It's not a bug — it's intentional. If you want parity with find, you need the -H flag: This is either genius or infuriating depending on whether you knew about it before debugging for 20 minutes. (I was debugging for 20 minutes.) Winner: fd — but learn the -H flag or it'll silently miss files. You need to know which files reference a string. Classic grep territory. And look at that consistency — rg has a standard deviation of 6.8ms vs grep's 50.8ms. rg is not just faster, it's predictably faster. Both returned exactly 3,500 matches: Unlike the filename search test, there's no hidden-file discrepancy here because our "test" string only appears in non-hidden files. But rg is still skipping hidden dirs and .git by default — it just happened to not matter in this case. Winner: rg — not even close. 3x faster with zero tuning. Sometimes you need to find all the dotfiles and dotdirs. Let's see who handles this best. All three found exactly 1,500 hidden files (we excluded .git/ objects from this count). Winner: fd — once you remember -H. This is one of fd and rg's marquee features — they auto-ignore .git, .svn, and anything in .gitignore. With find, you have to do it manually. That's a 2,000-file discrepancy. Here's the breakdown: fd and rg don't just skip .git — they skip all hidden files and directories. This is usually what you want (who searches node_modules/.cache/?), but it's worth knowing. Winner: fd — but understand what "ignore" actually means. Again, fd found half as many because .hidden_dir/subdir/deep_hidden_*.txt (500 files) is in a hidden directory. With -H: When neither tool is filtering (just "list everything"), the performance gap narrows: At raw directory traversal, they're basically tied. fd's advantage comes from its smarter filtering and parallelism, not from faster syscalls. Overall winner: fd + rg as a combo. Replace find with fd, replace grep with rg. You'll never look back. I see this pattern a lot: This is slow and redundant. Both fd and rg can filter by extension natively: One command. No pipes. Faster. fd is genuinely faster for filename searches — not by 10x like some blog posts claim, but a solid 2-2.5x in real-world use. That's worth the install. rg vs grep isn't even a contest — 3x faster with better defaults. If you take one thing from this article: alias grep=rg. Hidden files will bite you. Both fd and rg skip them by default. This is documented, but the first time you spend 30 minutes wondering why your .env file isn't showing up, you'll remember to add -H / --hidden. The ergonomics matter more than the speed. Even if fd were the same speed as find, I'd still use it. fd whatever is just nicer than find . -name '*whatever*'. The speed is gravy. hyperfine is a beautiful benchmarking tool. If you're ever debating tool performance, just run the damn benchmark instead of arguing on Hacker News. Last updated: May 2026. Tools tested: find 4.9.0, fd 10.2.0, grep 3.11, rg 15.1.0. Environment: WSL2 Ubuntu on NVMe SSD. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ du -sh . 143M . $ find . -type f | wc -l 10900 $ find . -type d | wc -l 1506 $ du -sh . 143M . $ find . -type f | wc -l 10900 $ find . -type d | wc -l 1506 $ du -sh . 143M . $ find . -type f | wc -l 10900 $ find . -type d | wc -l 1506 # GNU find find . -name '*.txt' # fd (extension filter) fd -e txt # ripgrep listing all files, piped to grep rg --files | grep '\.txt$' # GNU find find . -name '*.txt' # fd (extension filter) fd -e txt # ripgrep listing all files, piped to grep rg --files | grep '\.txt$' # GNU find find . -name '*.txt' # fd (extension filter) fd -e txt # ripgrep listing all files, piped to grep rg --files | grep '\.txt$' Benchmark 1: find . -name '*.txt' Time (mean ± σ): 94.5 ms ± 17.6 ms [User: 21.1 ms, System: 26.1 ms] Range (min … max): 74.8 ms … 139.9 ms 10 runs Benchmark 1: fd -e txt Time (mean ± σ): 39.9 ms ± 8.2 ms [User: 70.7 ms, System: 134.3 ms] Range (min … max): 30.8 ms … 55.3 ms 10 runs Benchmark 1: rg --files | grep '\.txt$' Time (mean ± σ): 117.6 ms ± 12.8 ms [User: 51.5 ms, System: 106.0 ms] Range (min … max): 104.8 ms … 144.0 ms 10 runs Benchmark 1: find . -name '*.txt' Time (mean ± σ): 94.5 ms ± 17.6 ms [User: 21.1 ms, System: 26.1 ms] Range (min … max): 74.8 ms … 139.9 ms 10 runs Benchmark 1: fd -e txt Time (mean ± σ): 39.9 ms ± 8.2 ms [User: 70.7 ms, System: 134.3 ms] Range (min … max): 30.8 ms … 55.3 ms 10 runs Benchmark 1: rg --files | grep '\.txt$' Time (mean ± σ): 117.6 ms ± 12.8 ms [User: 51.5 ms, System: 106.0 ms] Range (min … max): 104.8 ms … 144.0 ms 10 runs Benchmark 1: find . -name '*.txt' Time (mean ± σ): 94.5 ms ± 17.6 ms [User: 21.1 ms, System: 26.1 ms] Range (min … max): 74.8 ms … 139.9 ms 10 runs Benchmark 1: fd -e txt Time (mean ± σ): 39.9 ms ± 8.2 ms [User: 70.7 ms, System: 134.3 ms] Range (min … max): 30.8 ms … 55.3 ms 10 runs Benchmark 1: rg --files | grep '\.txt$' Time (mean ± σ): 117.6 ms ± 12.8 ms [User: 51.5 ms, System: 106.0 ms] Range (min … max): 104.8 ms … 144.0 ms 10 runs $ find . -name '*.txt' | wc -l 3500 $ fd -e txt | wc -l 3000 $ find . -name '*.txt' | wc -l 3500 $ fd -e txt | wc -l 3000 $ find . -name '*.txt' | wc -l 3500 $ fd -e txt | wc -l 3000 $ fd -H -e txt | wc -l 3500 $ fd -H -e txt | wc -l 3500 $ fd -H -e txt | wc -l 3500 # GNU grep (excluding .-weight: 500;">git manually, suppressing permission errors) grep -r 'test' . --exclude-dir=.-weight: 500;">git 2>/dev/null # ripgrep (auto-excludes .-weight: 500;">git, hidden dirs, binaries) rg 'test' # GNU grep (excluding .-weight: 500;">git manually, suppressing permission errors) grep -r 'test' . --exclude-dir=.-weight: 500;">git 2>/dev/null # ripgrep (auto-excludes .-weight: 500;">git, hidden dirs, binaries) rg 'test' # GNU grep (excluding .-weight: 500;">git manually, suppressing permission errors) grep -r 'test' . --exclude-dir=.-weight: 500;">git 2>/dev/null # ripgrep (auto-excludes .-weight: 500;">git, hidden dirs, binaries) rg 'test' Benchmark 1: grep -r 'test' . --exclude-dir=.-weight: 500;">git 2>/dev/null Time (mean ± σ): 302.7 ms ± 50.8 ms [User: 92.8 ms, System: 100.2 ms] Range (min … max): 228.6 ms … 370.8 ms 10 runs Benchmark 1: rg 'test' Time (mean ± σ): 103.2 ms ± 6.8 ms [User: 135.3 ms, System: 177.5 ms] Range (min … max): 95.3 ms … 116.0 ms 10 runs Benchmark 1: grep -r 'test' . --exclude-dir=.-weight: 500;">git 2>/dev/null Time (mean ± σ): 302.7 ms ± 50.8 ms [User: 92.8 ms, System: 100.2 ms] Range (min … max): 228.6 ms … 370.8 ms 10 runs Benchmark 1: rg 'test' Time (mean ± σ): 103.2 ms ± 6.8 ms [User: 135.3 ms, System: 177.5 ms] Range (min … max): 95.3 ms … 116.0 ms 10 runs Benchmark 1: grep -r 'test' . --exclude-dir=.-weight: 500;">git 2>/dev/null Time (mean ± σ): 302.7 ms ± 50.8 ms [User: 92.8 ms, System: 100.2 ms] Range (min … max): 228.6 ms … 370.8 ms 10 runs Benchmark 1: rg 'test' Time (mean ± σ): 103.2 ms ± 6.8 ms [User: 135.3 ms, System: 177.5 ms] Range (min … max): 95.3 ms … 116.0 ms 10 runs $ grep -r 'test' . --exclude-dir=.-weight: 500;">git 2>/dev/null | wc -l 3500 $ rg 'test' | wc -l 3500 $ grep -r 'test' . --exclude-dir=.-weight: 500;">git 2>/dev/null | wc -l 3500 $ rg 'test' | wc -l 3500 $ grep -r 'test' . --exclude-dir=.-weight: 500;">git 2>/dev/null | wc -l 3500 $ rg 'test' | wc -l 3500 # GNU find — no special flags needed, it sees everything find . -name '.*' -type f # fd — needs -H to include hidden stuff, regex anchor for dot-prefix fd -H -t f '^\.' # ripgrep — needs --hidden to see dotfiles rg --hidden --files | grep '^\.' # GNU find — no special flags needed, it sees everything find . -name '.*' -type f # fd — needs -H to include hidden stuff, regex anchor for dot-prefix fd -H -t f '^\.' # ripgrep — needs --hidden to see dotfiles rg --hidden --files | grep '^\.' # GNU find — no special flags needed, it sees everything find . -name '.*' -type f # fd — needs -H to include hidden stuff, regex anchor for dot-prefix fd -H -t f '^\.' # ripgrep — needs --hidden to see dotfiles rg --hidden --files | grep '^\.' Benchmark 1: find . -name '.*' -type f Time (mean ± σ): 104.3 ms ± 20.5 ms [User: 16.0 ms, System: 27.6 ms] Range (min … max): 79.0 ms … 144.5 ms 10 runs Benchmark 1: fd -H -t f '^\.' Time (mean ± σ): 40.5 ms ± 9.2 ms [User: 56.9 ms, System: 106.0 ms] Range (min … max): 26.6 ms … 57.2 ms 10 runs Benchmark 1: rg --hidden --files | grep '^\.' Time (mean ± σ): 118.8 ms ± 9.4 ms [User: 64.4 ms, System: 93.3 ms] Range (min … max): 106.9 ms … 135.0 ms 10 runs Benchmark 1: find . -name '.*' -type f Time (mean ± σ): 104.3 ms ± 20.5 ms [User: 16.0 ms, System: 27.6 ms] Range (min … max): 79.0 ms … 144.5 ms 10 runs Benchmark 1: fd -H -t f '^\.' Time (mean ± σ): 40.5 ms ± 9.2 ms [User: 56.9 ms, System: 106.0 ms] Range (min … max): 26.6 ms … 57.2 ms 10 runs Benchmark 1: rg --hidden --files | grep '^\.' Time (mean ± σ): 118.8 ms ± 9.4 ms [User: 64.4 ms, System: 93.3 ms] Range (min … max): 106.9 ms … 135.0 ms 10 runs Benchmark 1: find . -name '.*' -type f Time (mean ± σ): 104.3 ms ± 20.5 ms [User: 16.0 ms, System: 27.6 ms] Range (min … max): 79.0 ms … 144.5 ms 10 runs Benchmark 1: fd -H -t f '^\.' Time (mean ± σ): 40.5 ms ± 9.2 ms [User: 56.9 ms, System: 106.0 ms] Range (min … max): 26.6 ms … 57.2 ms 10 runs Benchmark 1: rg --hidden --files | grep '^\.' Time (mean ± σ): 118.8 ms ± 9.4 ms [User: 64.4 ms, System: 93.3 ms] Range (min … max): 106.9 ms … 135.0 ms 10 runs $ find . -name '.*' -type f -not -path './.-weight: 500;">git/*' | wc -l 1500 $ fd -H -t f '^\.' | wc -l 1500 $ find . -name '.*' -type f -not -path './.-weight: 500;">git/*' | wc -l 1500 $ fd -H -t f '^\.' | wc -l 1500 $ find . -name '.*' -type f -not -path './.-weight: 500;">git/*' | wc -l 1500 $ fd -H -t f '^\.' | wc -l 1500 # GNU find — manual .-weight: 500;">git exclusion find . -not -path './.-weight: 500;">git/*' -type f | wc -l # fd — auto-ignores .-weight: 500;">git AND hidden dirs fd -t f | wc -l # ripgrep — auto-ignores .-weight: 500;">git AND hidden dirs rg --files | wc -l # GNU find — manual .-weight: 500;">git exclusion find . -not -path './.-weight: 500;">git/*' -type f | wc -l # fd — auto-ignores .-weight: 500;">git AND hidden dirs fd -t f | wc -l # ripgrep — auto-ignores .-weight: 500;">git AND hidden dirs rg --files | wc -l # GNU find — manual .-weight: 500;">git exclusion find . -not -path './.-weight: 500;">git/*' -type f | wc -l # fd — auto-ignores .-weight: 500;">git AND hidden dirs fd -t f | wc -l # ripgrep — auto-ignores .-weight: 500;">git AND hidden dirs rg --files | wc -l Benchmark 1: find . -not -path './.-weight: 500;">git/*' -type f | wc -l Time (mean ± σ): 139.2 ms ± 16.5 ms [User: 27.9 ms, System: 37.6 ms] Range (min … max): 119.4 ms … 173.4 ms 10 runs Benchmark 1: fd -t f | wc -l Time (mean ± σ): 85.4 ms ± 10.2 ms [User: 82.9 ms, System: 118.2 ms] Range (min … max): 69.2 ms … 103.9 ms 10 runs Benchmark 1: rg --files | wc -l Time (mean ± σ): 129.0 ms ± 12.2 ms [User: 60.7 ms, System: 106.3 ms] Range (min … max): 111.4 ms … 151.3 ms 10 runs Benchmark 1: find . -not -path './.-weight: 500;">git/*' -type f | wc -l Time (mean ± σ): 139.2 ms ± 16.5 ms [User: 27.9 ms, System: 37.6 ms] Range (min … max): 119.4 ms … 173.4 ms 10 runs Benchmark 1: fd -t f | wc -l Time (mean ± σ): 85.4 ms ± 10.2 ms [User: 82.9 ms, System: 118.2 ms] Range (min … max): 69.2 ms … 103.9 ms 10 runs Benchmark 1: rg --files | wc -l Time (mean ± σ): 129.0 ms ± 12.2 ms [User: 60.7 ms, System: 106.3 ms] Range (min … max): 111.4 ms … 151.3 ms 10 runs Benchmark 1: find . -not -path './.-weight: 500;">git/*' -type f | wc -l Time (mean ± σ): 139.2 ms ± 16.5 ms [User: 27.9 ms, System: 37.6 ms] Range (min … max): 119.4 ms … 173.4 ms 10 runs Benchmark 1: fd -t f | wc -l Time (mean ± σ): 85.4 ms ± 10.2 ms [User: 82.9 ms, System: 118.2 ms] Range (min … max): 69.2 ms … 103.9 ms 10 runs Benchmark 1: rg --files | wc -l Time (mean ± σ): 129.0 ms ± 12.2 ms [User: 60.7 ms, System: 106.3 ms] Range (min … max): 111.4 ms … 151.3 ms 10 runs $ find . -not -path './.-weight: 500;">git/*' -type f | wc -l 10400 $ fd -t f | wc -l 8400 $ find . -not -path './.-weight: 500;">git/*' -type f | wc -l 10400 $ fd -t f | wc -l 8400 $ find . -not -path './.-weight: 500;">git/*' -type f | wc -l 10400 $ fd -t f | wc -l 8400 $ fd -H -t f | wc -l # with hidden files 10900 $ fd -H -t f | wc -l # with hidden files 10900 $ fd -H -t f | wc -l # with hidden files 10900 $ time find . -path '*deep*' -type f | wc -l 1000 real 0m0.088s $ time fd 'deep' -t f | wc -l 500 real 0m0.094s $ time find . -path '*deep*' -type f | wc -l 1000 real 0m0.088s $ time fd 'deep' -t f | wc -l 500 real 0m0.094s $ time find . -path '*deep*' -type f | wc -l 1000 real 0m0.088s $ time fd 'deep' -t f | wc -l 500 real 0m0.094s $ fd -H 'deep' -t f | wc -l 1000 $ fd -H 'deep' -t f | wc -l 1000 $ fd -H 'deep' -t f | wc -l 1000 $ time find . -type f | wc -l 10900 real 0m0.110s $ time fd -t f | wc -l 8400 real 0m0.116s $ time find . -type f | wc -l 10900 real 0m0.110s $ time fd -t f | wc -l 8400 real 0m0.116s $ time find . -type f | wc -l 10900 real 0m0.110s $ time fd -t f | wc -l 8400 real 0m0.116s rg --files | grep '\.ts$' | xargs rg 'useState' rg --files | grep '\.ts$' | xargs rg 'useState' rg --files | grep '\.ts$' | xargs rg 'useState' # Instead of the pipe combo: fd -e ts -x rg 'useState' # Or even better: rg --type ts 'useState' # Instead of the pipe combo: fd -e ts -x rg 'useState' # Or even better: rg --type ts 'useState' # Instead of the pipe combo: fd -e ts -x rg 'useState' # Or even better: rg --type ts 'useState' # Install the tools cargo -weight: 500;">install fd-find ripgrep hyperfine # or: -weight: 500;">apt -weight: 500;">install fd-find ripgrep hyperfine # Clone my setup (approximate recreation): mkdir -p /tmp/fd-benchmark && cd /tmp/fd-benchmark for i in $(seq 1 2000); do echo "test content line $i" > "file_${i}.txt"; done for i in $(seq 1 1000); do echo "hidden content $i" > ".hidden_${i}"; done mkdir -p .hidden_dir/subdir for i in $(seq 1 500); do echo "deep hidden $i" > ".hidden_dir/subdir/deep_hidden_${i}.txt"; done mkdir -p .-weight: 500;">git/objects for i in $(seq 1 500); do echo "-weight: 500;">git object $i" > ".-weight: 500;">git/objects/obj_${i}"; done # ... etc. Full generation script in the article source. # Run the benchmarks hyperfine -w 3 -r 10 "find . -name '*.txt'" "fd -e txt" hyperfine -w 3 -r 10 "grep -r 'test' . --exclude-dir=.-weight: 500;">git" "rg 'test'" # Install the tools cargo -weight: 500;">install fd-find ripgrep hyperfine # or: -weight: 500;">apt -weight: 500;">install fd-find ripgrep hyperfine # Clone my setup (approximate recreation): mkdir -p /tmp/fd-benchmark && cd /tmp/fd-benchmark for i in $(seq 1 2000); do echo "test content line $i" > "file_${i}.txt"; done for i in $(seq 1 1000); do echo "hidden content $i" > ".hidden_${i}"; done mkdir -p .hidden_dir/subdir for i in $(seq 1 500); do echo "deep hidden $i" > ".hidden_dir/subdir/deep_hidden_${i}.txt"; done mkdir -p .-weight: 500;">git/objects for i in $(seq 1 500); do echo "-weight: 500;">git object $i" > ".-weight: 500;">git/objects/obj_${i}"; done # ... etc. Full generation script in the article source. # Run the benchmarks hyperfine -w 3 -r 10 "find . -name '*.txt'" "fd -e txt" hyperfine -w 3 -r 10 "grep -r 'test' . --exclude-dir=.-weight: 500;">git" "rg 'test'" # Install the tools cargo -weight: 500;">install fd-find ripgrep hyperfine # or: -weight: 500;">apt -weight: 500;">install fd-find ripgrep hyperfine # Clone my setup (approximate recreation): mkdir -p /tmp/fd-benchmark && cd /tmp/fd-benchmark for i in $(seq 1 2000); do echo "test content line $i" > "file_${i}.txt"; done for i in $(seq 1 1000); do echo "hidden content $i" > ".hidden_${i}"; done mkdir -p .hidden_dir/subdir for i in $(seq 1 500); do echo "deep hidden $i" > ".hidden_dir/subdir/deep_hidden_${i}.txt"; done mkdir -p .-weight: 500;">git/objects for i in $(seq 1 500); do echo "-weight: 500;">git object $i" > ".-weight: 500;">git/objects/obj_${i}"; done # ... etc. Full generation script in the article source. # Run the benchmarks hyperfine -w 3 -r 10 "find . -name '*.txt'" "fd -e txt" hyperfine -w 3 -r 10 "grep -r 'test' . --exclude-dir=.-weight: 500;">git" "rg 'test'" - 10,900 total files - Minus 500 .-weight: 500;">git/ objects = 10,400 (what find reports with -not -path) - Minus 2,000 hidden files/dirs = 8,400 (what fd reports by default) - You're doing any filename-based search (replace find . -name) - You want smart defaults that skip .-weight: 500;">git, node_modules, hidden dirs - You want colored, readable output out of the box - You're typing into an interactive shell and want the shorter syntax - You're writing a portable script that needs to run on any Linux/Unix - You need the insane flexibility of -exec with complex predicates - You're on a machine where you can't -weight: 500;">install Rust tooling (embedded, minimal containers) - You need to search hidden files without remembering a flag - You're searching file contents (this is what it was built for) - You want .gitignore-aware searching (no more grepping through node_modules) - You need speed — it's SIMD-accelerated and uses memory mapping - You need POSIX compatibility in scripts - You're piping from stdin with complex flags (grep -oP with Perl regex is still great) - rg isn't installed and you can't -weight: 500;">install it - fd is genuinely faster for filename searches — not by 10x like some blog posts claim, but a solid 2-2.5x in real-world use. That's worth the -weight: 500;">install. - rg vs grep isn't even a contest — 3x faster with better defaults. If you take one thing from this article: alias grep=rg. - Hidden files will bite you. Both fd and rg skip them by default. This is documented, but the first time you spend 30 minutes wondering why your .env file isn't showing up, you'll remember to add -H / --hidden. - The ergonomics matter more than the speed. Even if fd were the same speed as find, I'd still use it. fd whatever is just nicer than find . -name '*whatever*'. The speed is gravy. - hyperfine is a beautiful benchmarking tool. If you're ever debating tool performance, just run the damn benchmark instead of arguing on Hacker News.