TL;DR:

  • fd is a modern replacement for find — faster, with a sane syntax and sensible defaults like respecting .gitignore and colouring results.
  • fzf is a fuzzy finder that integrates with your shell to give you interactive, incremental search over files, command history, git branches, and anything else you pipe into it.
  • Together they reduce the friction of navigating unfamiliar codebases and eliminate the need to remember arcane find flag syntax.

If you’ve ever typed find . -name "*.js" -not -path "*/node_modules/*" and wondered why this is a thing you have to know, fd is for you. If you’ve ever pressed Ctrl+R to search shell history and had to type the exact substring you remember from a command you ran two weeks ago, fzf is for you.

Neither tool is new — both have been around for years — but they’ve become foundational parts of the modern developer terminal setup, and many developers still don’t know about them.

fd: The find Replacement You’ve Been Waiting For

fd was written by David Peter (who also wrote bat, the cat replacement) and is available in most package managers.

# macOS
brew install fd

# Ubuntu/Debian
apt install fd-find
# Note: the binary is fdfind on Debian/Ubuntu — alias it: alias fd=fdfind

# Arch
pacman -S fd

# Windows (winget)
winget install sharkdp.fd

Why fd is better than find

The syntax is human. To find all TypeScript files containing “component” in their name:

# find
find . -name "*component*.ts" -not -path "*/node_modules/*"

# fd
fd component.ts

fd respects .gitignore by default, so it skips node_modules, .git, dist, and any other ignored directory without you having to add flags.

It’s fast. fd uses parallelism and avoids unnecessary work. On large codebases, the difference over find is noticeable.

It supports regex. fd treats its search term as a regex by default:

fd "test.*\.ts$"           # Find .ts files containing "test"
fd -e md                   # Find all .md files (extension filter)
fd -t d config             # Find directories named "config"
fd -t f -x wc -l           # Find files and run wc -l on each

Useful flags:

  • -H — include hidden files (.env, .github/)
  • -I — no-ignore mode (include gitignored files)
  • -e <ext> — filter by extension
  • -t f / -t d — filter by file or directory
  • -x <cmd> — execute a command for each result (parallel)
  • -X <cmd> — execute a command with all results as arguments

Practical examples

# Find all TODO comments across a project
fd -e ts -e js -x grep -l "TODO"

# Find recently modified files (last 2 days)
fd --changed-within 2d

# Find large log files
fd -e log -x du -sh | sort -h

# Find and delete all .DS_Store files
fd -H -I .DS_Store -x rm

# Find all test files
fd "_test|\.test\.|\.spec\."

fzf: A Fuzzy Finder for Everything

fzf is different from fd — it doesn’t find files itself. It’s an interactive fuzzy filter: you pipe things into it, and it lets you search through them interactively with keyboard shortcuts.

# macOS
brew install fzf
$(brew --prefix)/opt/fzf/install  # Adds shell key bindings

# Ubuntu/Debian
apt install fzf

# From source / cargo
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install

The installer adds three key bindings to your shell (bash or zsh):

  • Ctrl+T — fuzzy file search, inserts selected path into command line
  • Ctrl+R — fuzzy shell history search
  • Alt+C — fuzzy directory change (cd into selected directory)

The shell history use case alone is worth it

The default Ctrl+R behaviour in bash shows one history entry at a time in reverse order. With fzf, Ctrl+R opens an interactive fuzzy search across your entire shell history. You can type fragments from anywhere in the command — not just the beginning — and see all matches instantly.

Type docker run ubuntu and you’ll see every command you’ve ever run that contains those words, in any order, ranked by relevance. Select one with the arrow keys or Tab, press Enter, and it’s on your command line.

fd + fzf together

The most common integration: use fd to generate a list of files, pipe it to fzf for interactive selection:

# Interactive file open in editor
fd -t f | fzf | xargs $EDITOR

# Or add it as a shell function
fcd() { cd "$(fd -t d | fzf)" }

Set FZF_DEFAULT_COMMAND to use fd instead of find for fzf’s built-in file search:

# In your .bashrc or .zshrc
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"

Git integration

fzf works well with git commands that involve interactive selection:

# Interactive git branch checkout
git checkout $(git branch | fzf)

# Interactive git log with preview
git log --oneline | fzf --preview 'git show {1}'

# Interactive git add (stage individual files)
git status -s | fzf -m | awk '{print $2}' | xargs git add

For a fully featured git+fzf integration, look at the forgit plugin (available via Oh My Zsh or manual installation), which adds fzf-powered versions of git log, git add, git diff, git checkout, and more.

fzf in scripts

Because fzf is just a filter that reads from stdin and writes to stdout, it composes naturally with other tools:

# Pick from a list of running processes
kill $(ps aux | fzf | awk '{print $2}')

# Pick an AWS profile
AWS_PROFILE=$(grep '\[' ~/.aws/credentials | tr -d '[]' | fzf)

# Pick from npm scripts
npm run $(cat package.json | jq -r '.scripts | keys[]' | fzf)

Making It Stick

Install both tools today. The key bindings take about five minutes to configure. The payoff is immediate on Ctrl+R alone — if you’ve been using default history search, the fzf version will feel like a revelation the first time you use it.

Beyond that, the most productive approach is to add shortcuts incrementally. Start with Ctrl+R for history search. Then move to Ctrl+T for file insertion. Then build the fcd function for directory navigation. Each piece takes a few minutes and compounds into a meaningfully faster terminal workflow.

Both tools are well-maintained, under active development, and have large ecosystems of integrations with editors (Neovim, Helix), shell frameworks (Oh My Zsh, Starship), and other developer tools. Once you have the baseline working, there’s a lot of further customisation available if you want it — but the defaults are good enough to be immediately useful without any of it.