TL;DR:

  • fd replaces find with simpler syntax, much faster execution, .gitignore-awareness by default, and colourised output
  • ripgrep (rg) replaces grep with significantly faster performance, built-in file type filtering, .gitignore support, and better output formatting
  • Both are written in Rust and available on every platform via brew, apt, scoop, cargo
  • Neither requires learning new concepts — if you know find and grep, you already understand fd and ripgrep, the syntax is just cleaner

Most developers learn find and grep early and keep using them until they retire. They work. They’re on every system. Muscle memory is a real thing.

The case for switching to fd and ripgrep isn’t about correctness — the original tools are correct. It’s about the small frictions that add up: find syntax that never gets intuitive no matter how many times you look it up, grep that happily searches your node_modules folder and the .git directory, and speed that becomes noticeable on large codebases.

Here’s the practical case for both tools.

fd — A More Sensible find

Installation

# macOS
brew install fd

# Ubuntu/Debian
sudo apt install fd-find
# Note: the binary is fdfind on Debian/Ubuntu to avoid conflict with an existing fd package
# alias fd=fdfind in your shell config

# Windows
scoop install fd
winget install sharkdp.fd

# From source
cargo install fd-find

The basic difference

find syntax:

find . -name "*.ts" -type f
find . -name "*.ts" -not -path "*/node_modules/*" -type f
find . -mtime -1 -type f

fd syntax:

fd --extension ts
fd --extension ts  # node_modules excluded automatically
fd --changed-within 1d

The pattern in fd is a regex (or substring match if no regex characters) applied to the filename. You don’t prefix with -name, you don’t repeat the directory, and .gitignore entries (including node_modules) are excluded by default.

Most useful fd patterns

Find all TypeScript files:

fd -e ts
fd -e ts -e tsx

Find files matching a pattern in the name:

fd "test"           # Files with "test" in name
fd "test" src/      # Scoped to src/
fd "\.test\." src/  # Regex: files like "foo.test.ts"

Find recently modified files:

fd --changed-within 2h    # Last 2 hours
fd --changed-within 7d    # Last week

Find directories only:

fd -t d config    # Directories named config

Execute a command on each result:

fd -e json --exec cat {}     # Print each JSON file
fd -e log --exec rm {}       # Delete log files
fd -e ts --exec wc -l {}     # Line count of each TS file

Include ignored files (when you need to search node_modules or .git):

fd --no-ignore pattern
fd --hidden pattern     # Include hidden files/dirs
fd --hidden --no-ignore pattern  # Include everything

Pipe into other tools:

fd -e ts | head -20            # First 20 TypeScript files
fd -e ts | xargs grep "TODO"   # Search for TODO in all TS files
fd -e md | wc -l               # Count markdown files

Why the defaults matter

The fact that fd ignores .gitignore entries by default changes the experience in a meaningful way for most development workflows. Running fd pattern in a Node.js project doesn’t produce hundreds of matches from node_modules. Running it in a Python project skips __pycache__ and venv. You get results for code you actually care about.

Hidden files (dotfiles) are also excluded by default, which is usually right — you rarely want .git/COMMIT_EDITMSG in your search results.

ripgrep — grep That Respects Your Time

Installation

# macOS
brew install ripgrep

# Ubuntu/Debian
sudo apt install ripgrep
# Binary is rg

# Windows
scoop install ripgrep
winget install BurntSushi.ripgrep.MSVC

# From source
cargo install ripgrep

The basic difference

grep to search for a pattern in TypeScript files, excluding node_modules:

grep -r "useState" . --include="*.ts" --include="*.tsx" --exclude-dir=node_modules --exclude-dir=.git

rg equivalent:

rg "useState" -t ts

The .gitignore handling, directory exclusions, and file type selection are built in and on by default.

File type filtering

ripgrep has built-in definitions for dozens of file types:

rg "import" -t js       # JavaScript files
rg "SELECT" -t sql      # SQL files
rg "TODO" -t py         # Python files
rg "error" -t rust      # Rust files
rg "test" -t go         # Go files

# List all built-in type definitions
rg --type-list

You can add custom types:

rg --type-add "web:*.{html,css,js,ts}" "className" -t web

Output control

# Files with matches (no content)
rg -l "pattern"

# Count matches per file
rg -c "pattern"

# Case insensitive
rg -i "pattern"

# Fixed string (no regex interpretation)
rg -F "exact.string"

# Context lines (before/after)
rg -C 3 "pattern"     # 3 lines before and after
rg -A 2 "pattern"     # 2 lines after
rg -B 2 "pattern"     # 2 lines before

Multiline matching

# Match patterns that span multiple lines
rg -U "function\s+\w+\([^)]*\)\s*\{"

Including/excluding paths

rg "pattern" src/             # Only in src/
rg "pattern" -g "*.test.ts"   # Only test files
rg "pattern" -g "!*.test.ts"  # Exclude test files
rg "pattern" --no-ignore      # Include gitignored files
rg "pattern" --hidden         # Include hidden files

Replacing output

ripgrep can output results in a format that’s easy to pipe into editors:

# JSON output (useful for scripting)
rg "pattern" --json | jq '.data.lines.text'

# Null-terminated for xargs
rg -l0 "pattern" | xargs -0 sed -i 's/old/new/g'

Why ripgrep is faster

ripgrep uses several approaches that make it significantly faster than GNU grep on most codebases:

  • SIMD (vectorised) string matching — processes multiple bytes per CPU cycle on supported hardware
  • Parallel directory traversal — searches multiple directories concurrently
  • Memory-mapped file reading — skips file-by-file buffering overhead for large files
  • Smart encoding handling — detects binary files early and skips them rather than reading through them

On a codebase with a million lines of code and a cold file cache, the difference between grep -r and rg can be 10–50x depending on hardware. On warm caches (second search) the gap narrows but rg is consistently faster.

Combining fd and ripgrep

They compose well together:

# Search for a pattern only in recently modified TypeScript files
fd -e ts --changed-within 7d --exec rg "TODO: deprecated" {}

# Find all config files and search for a database URL
fd "config" -e json -e yaml | xargs rg "DATABASE_URL"

# Count total matches across file types
fd -e ts -e tsx | xargs rg -c "useEffect" | awk -F: '{sum += $2} END {print sum}'

Both tools also integrate well with fzf for interactive selection:

# Interactive file finder with preview
fd -t f | fzf --preview 'cat {}'

# Interactive content search with preview
rg --line-number "" | fzf --delimiter ':' --preview 'cat {1}' --preview-window '+{2}-5'

Shell Aliases and Configuration

In your .bashrc or .zshrc:

# Make fd work if installed as fdfind (Debian/Ubuntu)
which fdfind &>/dev/null && alias fd=fdfind

# Common aliases
alias rgs='rg -l'         # List files with matches
alias rgf='rg --fixed-strings'  # Fixed string search

# If you want to use rg as your default grep for interactive use
# (don't alias grep itself — some scripts depend on POSIX grep behaviour)
alias grep='rg'  # Optional, be aware of behavioural differences

Both tools respect a config file for persistent defaults:

ripgrep: ~/.config/ripgrep/rc (or $RIPGREP_CONFIG_PATH)

# ~/.config/ripgrep/rc
--max-columns=150
--max-columns-preview
--smart-case

fd doesn’t have a config file but respects .gitignore and .ignore files in the directory tree.

Editor Integration

Neovim: telescope.nvim uses ripgrep for its live grep functionality. Install ripgrep and it works automatically. The fd binary powers file finder in Telescope too.

VS Code: ripgrep is bundled with VS Code and used for the built-in search. You’re already using it — the option to use an external ripgrep binary is available in settings.

Helix: Uses ripgrep for global search by default.

Emacs: consult-ripgrep provides live ripgrep search in minibuffer. projectile uses fd for file finding.

Zed: Uses ripgrep for project search. Configured automatically if installed.

For most editors, the tools are most powerful not through the editor’s built-in integration but by dropping into a terminal and running them directly — especially for cross-project searches, bulk operations, or scripted analysis.

The Two-Minute Install

If you haven’t installed these yet:

# macOS
brew install fd ripgrep

# Ubuntu/Debian
sudo apt install fd-find ripgrep
echo "alias fd=fdfind" >> ~/.bashrc && source ~/.bashrc

# Then test:
fd -e md         # Should list markdown files
rg "TODO" -t ts  # Should search TypeScript for TODOs

Five minutes of experimenting with your own codebase will demonstrate more than any benchmark.

References