I used to open files the slow way: cd into a directory, ls to see what’s there, maybe find with a half-remembered filename, then finally pass it to whatever program I needed. Every time, I’d lose a few seconds hunting for the right path. Multiply that by dozens of files a day, and it adds up to real friction.

Then I found fzf, and file selection stopped being a chore. fzf is a fuzzy finder that turns file selection into a fast, interactive search.

What fzf actually is

fzf is a command-line fuzzy finder. It takes a list of things (files, directories, command history, git branches, anything) and lets you interactively narrow that list by typing a few characters. You don’t need to remember exact filenames or paths. Type fragments in any order, and fzf finds matches instantly.

Junegunn Choi built it in Go. It ships as a single binary with no dependencies. You can install it anywhere, and it just works.

The word “fuzzy” is the important part. Traditional search tools like grep or find require you to know what you’re looking for. fzf flips that around. You start typing, and it shows you what matches. The characters can sit anywhere in the filename. Type cntlr and it’ll match controller. Type idx and it’ll find index.md.

The mental model: fzf is a filter

The concept that makes fzf click is simple: fzf is a filter. It reads lines from standard input, lets you pick one interactively, and writes your selection to standard output.

graph TB A[Input: list of items] --> B[fzf: interactive filter] B --> C[Output: your selection] style A fill:#e1f5fe style B fill:#f3e5f5 style C fill:#e8f5e8

This is textbook Unix philosophy. fzf doesn’t know or care what it’s filtering. It doesn’t know what you’ll do with the result. It does one thing well: let you pick from a list, fast.

That’s why it’s so versatile. Any program that accepts a filename as an argument can benefit from fzf. The pattern is always the same:

command $(fzf)

fzf pops up, you pick a file, and the shell passes the result to command. That’s the whole trick.

Why fuzzy matching changes how you work

Traditional file finding requires you to think in paths. You have to remember the directory structure, the exact filename, maybe even the extension. Your brain does work that a computer should be doing.

Fuzzy matching lets you think in fragments. You remember that the file had “config” and “prod” somewhere in the name? Type confprod and fzf will find config/production.yaml buried three directories deep.

Under the hood, fzf uses a modified Smith-Waterman algorithm (borrowed from bioinformatics, of all places) to score matches. Every character in your query must appear in the result, in order, though they can sit apart. fzf scores matches based on how tightly the characters cluster, whether they appear at word boundaries, and how close to the start of the string they sit.

None of that matters for daily use. But it explains why fzf feels so good: the best match almost always floats to the top.

Opening files in different programs

This is where fzf earns its keep in daily work. The command $(fzf) pattern works with anything.

Neovim and Vim

nvim $(fzf)

That’s it. fzf shows your file tree, you type a few characters, hit enter, and you’re editing. No :e followed by tab-completing through nested directories. No :find with wildcards.

For filenames with spaces (they exist, and they’re annoying), the safer version is:

fzf --print0 | xargs -0 -o nvim

macOS apps

The open command on macOS pairs naturally with fzf:

open $(fzf)

This opens the selected file in its default application. A .pdf opens in Preview. A .png opens in your image viewer. A .html opens in your browser.

Want a specific app? Use the -a flag:

open -a "Preview" $(fzf)
open -a "Visual Studio Code" $(fzf)
open -a "Figma" $(fzf)

Any program

The pattern generalizes to everything:

cat $(fzf)        # print file contents
code $(fzf)       # open in VS Code
less $(fzf)       # page through a file
cp $(fzf) ~/tmp/  # copy a file somewhere

Once you internalize $(fzf) as “let me pick a file,” you’ll start using it everywhere without thinking.

The preview window

fzf’s live preview earns its keep. When you’re scanning a list of similarly named files, seeing the contents helps you pick the right one.

fzf --preview 'cat {}'

fzf replaces the {} placeholder with the currently highlighted item. As you move through the list, the preview updates in real time.

For syntax-highlighted previews, bat is a better choice than cat:

fzf --preview 'bat --color=always {}'

I use this constantly. When I have six files named config.yaml in different directories, the preview tells me which one I want without opening each one.

How fzf fits inside editors

LazyVim and Neovim plugins

fzf’s influence goes beyond the shell. Inside Neovim, fuzzy finding has become the default way to navigate code.

LazyVim (a popular Neovim configuration) switched its default picker from Telescope to fzf-lua starting with version 14. The reason was speed. In large repositories, fzf-lua (which uses fzf’s matching algorithm) filters results noticeably faster than Telescope’s Lua-based matcher.

Inside LazyVim, pressing <leader>ff opens a fuzzy file finder that works the same way as fzf in the terminal. You type fragments, results narrow down, and you hit enter to open the file. The mental model is identical, just wrapped in a Neovim buffer instead of a terminal window.

This is fzf’s real legacy in the editor space: it normalized the idea that file navigation should be search-first, not tree-first. You shouldn’t need to browse a file explorer to find something. You should type what you remember and let the computer do the rest.

VS Code and other editors

VS Code’s Ctrl+P (or Cmd+P on macOS) is essentially the same concept. JetBrains has “Search Everywhere.” Emacs has Helm and Ivy. Every serious editor adopted fuzzy file finding because it works.

fzf didn’t invent fuzzy matching. But it made it a composable, standalone tool that works outside any single editor. That’s the difference. You learn one interaction pattern and it works in your shell, your editor, your scripts, and your aliases.

Shell integrations that matter

After installing fzf, you can enable shell key bindings that change how you use your terminal.

Ctrl+T Fuzzy-find a file or directory and paste the path onto your current command line. Start typing vim, hit Ctrl+T, pick a file, and the path appears right where your cursor was.
Ctrl+R Fuzzy-search your command history. Instead of pressing up-arrow forty times or running history | grep, you type a few characters from any part of the command you remember.
Alt+C Fuzzy-find a directory and cd into it immediately. No more cd followed by tab-completing through five levels of nested folders.

Ctrl+R alone is worth the install. I’ve wasted cumulative hours of my life scrolling through shell history looking for that one kubectl command I ran last week. With fzf, I type kube deploy prod and it finds it.

Building your own workflows

Because fzf is a filter, you can feed it anything. This is where it gets interesting.

Want to fuzzy-find a git branch and check it out?

git branch | fzf | xargs git checkout

Want to pick a running process and kill it?

ps aux | fzf | awk '{print $2}' | xargs kill

Want to browse your bookmarks and open one?

cat ~/.bookmarks | fzf | xargs open

The pattern stays the same. Generate a list, pipe it to fzf, do something with the selection. Once you see it, you start spotting opportunities for it everywhere.

Trade-offs and limitations

fzf is great, but it has limits.

  • Large result sets can overwhelm. If you run fzf at the root of a massive monorepo with hundreds of thousands of files, startup can be slow. You can mitigate this with fd (a faster find alternative) as the input source, or by scoping your search to specific directories.

  • Fuzzy matching can be too fuzzy. Sometimes you know the exact filename and find or fd with an exact match is faster. fzf’s strength is when you don’t remember the exact name.

  • It’s interactive. fzf requires a human to make a selection. It’s poorly suited to scripted file operations where you need deterministic behavior.

  • Terminal-only. If your workflow lives entirely in a GUI, fzf won’t help much. It shines for people who spend real time in a terminal.

The core idea

fzf solves a problem so common that most people overlook it: the friction of specifying a file. Every time you cd, ls, tab-complete, or browse a file tree, you’re doing work that a fuzzy finder can eliminate.

The mental model is dead simple. fzf is a filter. It reads lines, you pick one, it outputs your choice. Plug it into any command, any workflow, any script. The $(fzf) pattern works everywhere because Unix pipes work everywhere.

Once you start using it, you’ll wonder how you tolerated the old way of finding files. I did.

Next steps

  • Install fzf from the official repository and run the install script to enable shell key bindings.
  • Try nvim $(fzf) and open $(fzf) to feel the difference in your own workflow.
  • Look into fd as a faster file-finding backend for fzf.
  • If you use Neovim, explore fzf-lua or enable the fzf extra in LazyVim for in-editor fuzzy finding.
  • Read about configuring my shell for more terminal productivity tips.

References

  • fzf official repository, the source and documentation for fzf by Junegunn Choi.
  • bat, a cat replacement with syntax highlighting that pairs well with fzf’s preview feature.
  • fd, a fast, user-friendly alternative to find that works as an fzf input source.
  • LazyVim, a Neovim configuration that uses fzf-lua as its default fuzzy finder.
  • Smith-Waterman algorithm, the bioinformatics algorithm that inspired fzf’s matching approach.