Back

Why zsh Is Slow to Start (and How to Fix It)

Why zsh Is Slow to Start (and How to Fix It)

You open a new terminal tab. You wait. One second passes, then two, maybe three. Your zsh prompt finally appears. This frustrating delay isn’t the shell’s fault—it’s almost always your configuration.

A vanilla zsh installation starts in roughly 50–100 milliseconds. When startup balloons to multiple seconds, the culprits are predictable: completion system overhead, synchronous plugin loading, heavy themes, and language version managers like nvm or pyenv. This guide shows you how to identify what’s actually slow and fix it without rewriting your entire setup.

Key Takeaways

  • A stock zsh starts in 50–100ms. Multi-second delays come from your .zshrc, not the shell itself.
  • Use zsh/zprof and /usr/bin/time zsh -i -c exit to pinpoint the real bottlenecks before changing anything.
  • Language version managers (nvm, pyenv, conda) are the most common offenders—lazy-load them for immediate gains.
  • Call compinit exactly once, trim unused plugins, and use a lightweight theme to shave off additional startup time.

Profile Before You Optimize

Guessing wastes time. Zsh includes a built-in profiler that reveals exactly where your startup time goes.

Add this to the first line of your ~/.zshrc:

zmodload zsh/zprof

Add this to the last line:

zprof

Open a new terminal. You’ll see output like this:

num  calls    time           self           name
1)   1        442.05  84.53%  254.54  48.68%  nvm_auto
2)   2        187.51  35.86%   91.66  17.53%  nvm
3)   1         75.70  14.48%   64.37  12.31%  nvm_ensure_version_installed

The self column shows time spent in each function excluding calls to other profiled functions. Target the biggest numbers first.

For wall-clock measurement, use:

/usr/bin/time zsh -i -c exit

Run this multiple times—the first invocation may include cold-cache effects.

The Most Common Bottlenecks

Language Version Managers (nvm, pyenv, conda)

These are the single biggest cause of slow zsh startup. The default nvm initialization alone can add 300–500ms.

The fix: lazy loading. Don’t initialize these tools until you actually call them.

For nvm, replace the standard initialization block with:

export NVM_DIR="$HOME/.nvm"

nvm() {
  unfunction nvm
  [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
  nvm "$@"
}

Using a wrapper function instead of an alias is more robust here. An alias-based approach can break when nvm is invoked indirectly (for example, by a script or another function), because aliases are only expanded in interactive, top-level command positions. A function, on the other hand, behaves like a real command in every context.

If you use Oh-My-Zsh, enable the nvm plugin with lazy loading:

zstyle ':omz:plugins:nvm' lazy yes
plugins=(git nvm)

This approach dropped one user’s startup from 430ms to 140ms—a 70% improvement.

compinit Called Multiple Times

The compinit function initializes zsh’s completion system. It’s expensive, and frameworks often call it more than once by accident.

Best practice: Call compinit exactly once, after all $fpath modifications are complete.

# Add to fpath FIRST
fpath=($ZSH_CUSTOM/plugins/zsh-completions/src $fpath)

# THEN initialize completions
autoload -Uz compinit && compinit

If your .zcompdump file is stale or corrupted, regenerate it:

rm -f ~/.zcompdump
compinit

Using compinit -C skips security checks and is faster, but understand the trade-off before enabling it: it won’t warn you if a completion file has been tampered with or is owned by another user.

Too Many Plugins

Each plugin sources files synchronously during startup. Audit your plugin list ruthlessly.

Check which plugins you actually use. The github and brew plugins are notoriously slow. Remove anything you don’t use weekly.

Heavy Themes

Complex themes that query git status, check network resources, or run subprocesses add noticeable latency.

If you use Powerlevel9k, switch to Powerlevel10k. It’s a drop-in replacement that renders prompts 10–100x faster. Enable its Instant Prompt feature for perceived-instant startup.

Oh-My-Zsh Auto-Update Checks

Oh-My-Zsh’s update check runs on every shell launch and can dominate startup time. Disable it:

DISABLE_AUTO_UPDATE="true"

You can still run omz update manually whenever you want.

Quick Wins for Faster Startup

  1. Reduce plugin count – Remove unused plugins from your configuration.
  2. Lazy-load version managers – nvm, pyenv, and conda should load on demand.
  3. Call compinit once – After all fpath modifications are done.
  4. Use a lightweight theme – Or enable Powerlevel10k’s Instant Prompt.
  5. Disable auto-updates – Check for updates manually instead.

What “Fast” Actually Looks Like

A well-optimized zsh configuration with Oh-My-Zsh should start in 150–300ms. Without a framework, 50–100ms is achievable. If you’re above 500ms, something specific is wrong—and zprof will show you what.

Conclusion

Don’t optimize blindly. Profile your startup, identify the real bottlenecks, and apply targeted fixes. Most developers can cut their zsh startup time by 50–80% in under ten minutes by lazy-loading one version manager and removing a few unused plugins.

Remember to remove the zprof lines from your .zshrc when you’re done measuring.

FAQs

No. A bare zsh installation starts just as fast as bash, typically in under 100 milliseconds. The perceived slowness almost always comes from what your .zshrc loads at startup, such as plugins, themes, and version managers, not from the shell itself.

Not in practice. Lazy loading means nvm and its managed Node version are loaded the first time you run nvm, node, npm, or npx in a session. After that initial call, everything works exactly as before. The only difference is that the shell starts faster.

Run zsh with profiling enabled by adding zmodload zsh/zprof to the top and zprof to the bottom of your .zshrc. In the output, look for multiple calls next to compinit or compdump. If the calls column shows a number greater than one, your configuration is initializing completions redundantly.

Not necessarily. Oh-My-Zsh itself adds modest overhead. The slowdowns come from specific plugins and themes loaded through it. Trimming your plugin list, lazy-loading version managers, and switching to a fast theme like Powerlevel10k usually bring startup well under 300 milliseconds without abandoning the framework.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay