Why I Switched from Zsh + P10k to Fish Shell (And Why You Should Too)

The Problem: 500ms Shell Startup
If you run zsh with Oh My Zsh and Powerlevel10k, you already know the feeling. You open a new terminal tab and wait. You switch tmux sessions and wait. You type faster than your shell can render.
The numbers tell the story:
# Zsh + Oh My Zsh + P10k
time zsh -i -c exit
# real 0m0.487s
# Fish shell (default config)
time fish -c exit
# real 0m0.038s
That is a 12x difference. Half a second versus 40 milliseconds.
Half a second sounds small until you multiply it by every new shell, every tmux session, every fzf picker that spawns a subshell. It adds up to minutes of dead time every day.
When Your Shell Cannot Keep Up With You
Here is a scenario that pushed me over the edge. I use tmux-sessionizer (or similar tools like tmux-session-wizard) to manage projects as separate tmux sessions via fzf.
My workflow was:
- Pick a session via fzf
- Type
nvimand hit Enter - Hit a keybinding to open file search
- Type the search query
Sounds fast. Except with Zsh, I would type all of that faster than the shell could process it. Then I would sit there for a full second watching my keystrokes replay in slow motion as everything loaded. The shell was the bottleneck, not me.
With Fish, that entire sequence happens instantly. Keystrokes arrive at their destination the moment I press them.
Autocomplete: From Sluggish to Instant
Zsh autocomplete trained me to press Tab multiple times. The completion engine was slow enough that my first Tab press seemed to do nothing, so I would mash Tab repeatedly.
After switching to Fish, that habit backfired. Fish autocomplete is so fast that each Tab press actually registers. I would end up with duplicate arguments from git add because completions resolved instantly and every extra Tab added another match.
# Fish: Tab completion is instant
git add <Tab>
# Immediately shows matching files
# Zsh: Tab completion has visible delay
git add <Tab>
# ... wait ...
# ... wait ...
# Results appear
This is not a benchmark game. It is a genuine workflow difference you feel in your hands.
The Migration Fear (And Why It Was Overblown)
The biggest reason I resisted Fish for years: it uses its own scripting language. Not POSIX-compatible. Not Bash. Fish syntax.
My mental objections:
- "What about all my Oh My Zsh aliases?"
- "I have custom functions that took years to build"
- "I will have to relearn everything"
Here is what actually happened: I realized I only used aliases for Git and Kubernetes. That is it. Everything else was a handful of custom functions and one-liners.
I fed my .zshrc to Claude and had everything ported to Fish in 30 minutes. Every alias, every function, working.
# Fish aliases (abbreviations)
abbr -a g 'git'
abbr -a ga 'git add'
abbr -a gc 'git commit'
abbr -a gp 'git push'
abbr -a gst 'git status'
abbr -a k 'kubectl'
abbr -a kgp 'kubectl get pods'
abbr -a kns 'kubectl config set-context --current --namespace'
Fish abbr (abbreviations) are actually better than aliases. They expand inline so you can see and edit the full command before running it. You always know exactly what will execute.
Fish Scripting: Less Insane Than Bash
Yes, Fish has its own syntax. No, you do not need to learn much of it.
For DevOps daily work, you need:
- Conditionals
- Loops over files and strings
- Variable expansion
That is about it. Here is the honest comparison:
# Bash: loop over files
for f in *.log; do
echo "Processing $f"
grep ERROR "$f" | wc -l
done
# Fish: loop over files
for f in *.log
echo "Processing $f"
grep ERROR "$f" | wc -l
end
# Bash: conditional
if [ -f /etc/kubernetes/admin.conf ]; then
export KUBECONFIG=/etc/kubernetes/admin.conf
fi
# Fish: conditional
if test -f /etc/kubernetes/admin.conf
set -gx KUBECONFIG /etc/kubernetes/admin.conf
end
The syntax is different, but it is not harder. Arguably it is more readable. And let us be honest: Bash syntax is not exactly a paragon of good language design either. We just got used to it.
What You Get for Free
Fish ships with features that Zsh needs plugins for:
| Feature | Zsh | Fish |
|---|---|---|
| Syntax highlighting | zsh-syntax-highlighting plugin | Built-in |
| Autosuggestions | zsh-autosuggestions plugin | Built-in |
| Tab completions | Basic + manual configs | Built-in for 300+ commands |
| Web-based config | None | fish_config opens a browser UI |
| Man page completions | None | Auto-generated from man pages |
No plugin manager. No framework. No .zshrc that sources 15 files. Fish works out of the box.
Prompt: Tide Instead of P10k
If you love the Powerlevel10k look, Tide gives you the same two-line prompt with segments, icons, and git info. It is built for Fish and just as configurable.
Or use Starship, which works across all shells. We covered this comparison in our Tide vs Starship post.
The Honest Tradeoffs
Fish is not perfect. Here is what you give up:
POSIX compatibility. You cannot paste Bash one-liners from Stack Overflow directly. Most work, but some need minor tweaks ($(cmd) becomes (cmd), export becomes set -gx).
Some niche plugins. The Zsh plugin ecosystem is larger. If you depend on something very specific, check if a Fish equivalent exists first.
Team scripts. If your team shares Bash scripts, keep Bash for scripts. Fish is for interactive use. You can (and should) still write scripts with #!/bin/bash.
Muscle memory. The first week is rough. You will type export instead of set -gx and $(...) instead of (...). After a week, the new syntax is automatic.
Quick Migration Checklist
1. Install Fish
brew install fish # macOS
sudo apt install fish # Ubuntu/Debian
2. Try it first (do NOT set as default yet)
fish
3. Port your aliases
# Copy your aliases from .zshrc
# Convert to Fish abbreviations
# Save in ~/.config/fish/config.fish
4. Install a prompt (optional)
fisher install IlanCosman/tide # Tide
# or
brew install starship # Starship (cross-shell)
5. Test for a week
6. Set as default when ready
chsh -s (which fish)
Performance Comparison Summary
| Metric | Zsh + OMZ + P10k | Fish (default) |
|---|---|---|
| Shell startup | ~500ms | ~40ms |
| Tab completion | Visible delay | Instant |
| Syntax highlighting | Plugin required | Built-in |
| Autosuggestions | Plugin required | Built-in |
| Plugin count needed | 5-10 typical | 0-2 typical |
| Config complexity | High (.zshrc, .p10k.zsh) | Low (config.fish) |
Bottom Line
The switch from Zsh to Fish is a net positive for anyone who values terminal speed. The migration cost is a few hours (less with an AI assistant). The daily payoff is a shell that never makes you wait.
If you are typing faster than your terminal can handle, Fish fixes that.
At Akmatori, our AI agents live inside terminals, running diagnostics and executing remediation commands. Shell performance matters when your agent is spawning hundreds of subshells during incident investigation. Fish helps keep that fast.
