fzf Philosophy and Design
fzf's power comes from its philosophy: do one thing, do it interactively, and compose with everything else. Understanding this design principle makes you a far more effective fzf user.
The Unix Filter Model
Unix tools are built around the idea of small, composable programs connected by pipes. fzf is a perfect Unix citizen:
STDIN → fzf (interactive select) → STDOUT
It has no opinion about:
- What data it receives (files, processes, commits, URLs — anything)
- What the caller does with the output (open, delete, copy, pipe further)
- What shell or editor you use
This is intentional. By staying minimal, fzf works everywhere.
"Interactive" vs "Batch" Filtering
| Mode | Tool | Use case |
|---|---|---|
| Batch | grep, awk, sed | You know the pattern; automate it |
| Interactive | fzf | You need to see the options and choose |
# Batch: you know exactly what you want
grep "ERROR" /var/log/syslog | tail -20
# Interactive: you want to browse and pick
grep "ERROR" /var/log/syslog | fzf --preview 'echo {}' --preview-window=down:3
The Three Rules of fzf Design
Rule 1: Accept Any Line-Based Input
fzf does not care if the input is filenames, log lines, JSON, git SHAs, IP addresses, or hostnames. It treats everything as opaque text lines and lets you filter them.
# Works with any list:
cat /etc/passwd | fzf # users
docker images | fzf # images
kubectl get pods | fzf # pods
history | fzf # commands
curl -s 'https://api.github.com/repos/torvalds/linux/commits' \
| jq -r '.[].commit.message' | fzf # commit messages
Rule 2: Output is Consumable by Other Tools
fzf outputs what you select to stdout. The consuming command decides what to do with it:
# Select, then open
fzf | xargs nvim
# Select, then delete
find . -name "*.tmp" | fzf -m | xargs rm
# Select, then copy to clipboard
ls | fzf | xclip -selection clipboard
Rule 3: Behavior is Composable via Flags
fzf's extensive --flag system lets you compose complex behaviors at the call site, not inside fzf itself:
fzf \
--multi \ # allow multiple selections
--preview 'bat --color=always {}' \ # preview command
--preview-window 'right:60%' \ # preview layout
--bind 'ctrl-/:toggle-preview' \ # custom keybinding
--header 'Select files to edit' \ # status header
--prompt 'Files> ' \ # custom prompt
--pointer '▶' \ # selection pointer
--marker '✓' # multi-select marker
What fzf Deliberately Omits
| Feature | Why fzf omits it |
|---|---|
| Built-in file browser | Use find, fd, or ls as input — more flexible |
| Config file | Use FZF_DEFAULT_OPTS env variable — scriptable |
| Plugin system | Use shell functions — composable with existing tools |
| State persistence | Use the shell's history and aliases |
| GUI version | It's a TUI tool — intentionally terminal-native |
The Mental Model: "Interactive xargs"
If you know xargs, you can think of fzf as interactive xargs:
# xargs: non-interactive, process all matching items
find . -name "*.log" | xargs grep "ERROR"
# fzf + xargs: interactive, YOU pick which log to grep
find . -name "*.log" | fzf | xargs grep "ERROR"
Composability in Practice
flowchart TD
A["Any command\nthat outputs lines"] --> FZF["fzf\n(you pick)"]
FZF --> B["Any command\nthat accepts lines"]
subgraph Sources [Example Sources]
S1["find / fd"]
S2["git log"]
S3["ps aux"]
S4["docker ps"]
S5["history"]
S6["cat ~/.ssh/config"]
end
subgraph Consumers [Example Consumers]
C1["nvim / vim"]
C2["git checkout"]
C3["kill"]
C4["docker exec"]
C5["ssh"]
C6["xargs rm"]
end