There is nothing more frustrating than opening a new terminal tab and staring at a blinking cursor for two full seconds before you can actually type a command. If you’ve spent months adding plugins and custom aliases, you’ve likely noticed the bloat. Learning how to speed up zsh startup is essential for maintaining a fluid development workflow.
In my own experience, my Zsh startup time peaked at nearly 3 seconds after I installed several heavy plugins. By following the steps in this guide, I managed to bring it down to under 100ms. Here are the most effective tips to optimize your shell.
1. Profile Your Startup Time
Before you start deleting things, you need to know what is actually causing the lag. You can’t fix what you can’t measure. I use the built-in zprof module to find the culprits.
# Add this to the very top of your .zshrc
zmodload zsh/zprof
# ... the rest of your config ...
# Add this to the very bottom of your .zshrc
zprof
Once you restart your terminal, Zsh will print a detailed table showing exactly which functions took the longest to execute. As shown in the terminal output image below, this allows you to pinpoint whether a specific plugin or a slow nvm initialization is the bottleneck.
2. Optimize Oh My Zsh (or Switch)
Oh My Zsh is fantastic for beginners, but it can be heavy. If you have 20+ plugins enabled, you’re loading a massive amount of script into memory every time you open a tab. I recommend auditing your plugin list and removing anything you don’t use daily.
If you find that the framework itself is the problem, you might want to explore some oh my zsh alternatives like Zinit or Antigen, which offer faster loading mechanisms.
3. Lazy Load NVM (The Biggest Culprit)
If you use Node Version Manager (NVM), you’ve probably noticed it adds a massive delay. By default, NVM scans directories for .nvmrc files on every shell start. I solved this by lazy loading NVM—only initializing it when I actually call node or npm.
# Replace your standard NVM init with this
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && {
nvm() { unset -f nvm; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm "$@"; }
npm() { unset -f npm; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; npm "$@"; }
node() { unset -f node; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; node "$@"; }
}
4. Use a Faster Plugin Manager
Traditional plugin managers load scripts sequentially. Modern managers like Zinit or Zplugin support “turbo mode,” which loads plugins in the background after the prompt has already appeared. This makes the perceived startup time nearly instantaneous.
5. Avoid Heavy Command Substitutions
Calling external binaries in your prompt or config—like $(git branch) or $(date)—can add milliseconds to every new shell. Use Zsh’s built-in variables whenever possible. If you are deciding between shells for your Mac, check out my breakdown of zsh vs bash for mac developers to see which handles these tasks more efficiently.
6. Minimize External Tool Calls
Every time you call eval "$(some_tool init zsh)", you are spawning a subshell. Tools like starship, pyenv, and conda are notorious for this. Check if these tools provide a static initialization script or a faster way to load.
7. Compile Your Zshrc
Zsh can compile your configuration into a binary format (.zwc) which loads significantly faster than parsing a text file. You can do this manually using zcompile.
zcompile ~/.zshrc
8. Use a Lightweight Prompt
Complex prompts that check for Git status, Kubernetes context, and AWS profiles can lag. While Starship is fast (written in Rust), a simple, custom-built prompt is always faster. If you’re using a theme that does too many network or disk checks, consider simplifying it.
9. Optimize PATH Lookups
If your $PATH is cluttered with dead directories or network drives, Zsh spends time searching for binaries that don’t exist. Periodically clean your .zshrc and remove paths to tools you no longer use.
10. Move Heavy Logic to .zprofile
Remember that .zshrc runs for every single interactive shell (including subshells). Move environment variables and one-time setup logic to .zprofile, which only runs during login. This prevents redundant execution of heavy scripts.
Common Mistakes to Avoid
- Over-pluginning: Installing a plugin for a feature you could achieve with a 1-line alias.
- Blindly Copy-Pasting: Adding
evalstatements to your config without knowing what they do. - Ignoring the Cache: Not clearing out old compiled files after major config changes.
Measuring Success
To verify your improvements, run this command in your terminal:
time zsh -i -c exit
The real time value tells you exactly how long it takes for Zsh to initialize and exit. Aim for under 0.20s for a snappy feel. If you’re still seeing lag, go back to zprof and look for the new slowest function.