Bringing down my ZSH load times from ~3.1s to ~230ms

speckx2 pts0 comments

Bringing down my ZSH load times from ~3.1s to ~230ms

Skip to content

Bringing down my ZSH load times from ~3.1s to ~230ms

Jun 17, 2026

in Computers

This year, the amount of time I spend in the terminal has gone significantly up thanks to Claude Code.

Couple of things:

I have this insane tendency to close tabs as soon as I’m done with them

Over the year, my work computer ZSH dotfiles have gotten extremely bloated

I got to a point where I couldn’t tolerate how slow opening a new shell session got. Naturally, I decided to fix it.

First, I needed to truly understand how slow my zsh was.

I ran time to get the exact load time of my zsh.

time zsh -il -c exit

-i: interactive mode; needed to understand prompts, completions, aliases, etc.<br>-l: login mode; needed to load /etc/zprofile and ~/.zprofile, etc.) in addition to .zshrc (I only use .zshrc and a ~/shell_overrides.sh file to load computer specific config)<br>-c exit: run the exit command immediately and quit

These give you a true load time. The result didn’t come as a surprise.

3.1 seconds load time. It’s a ridiculously high number. No wonder I hated opening a new terminal session so much. For reference, my personal MacBook Air loads my zsh session in ~150ms.

I wanted to get to that number. At least as close as I could.

Next, I wanted to profile the full zsh setup. If you know me, I’m a minimalist. I don’t have any fancy setup. So, I knew the slowness was caused by the programs I was using.

So, I ran a profile. The way you do that in your zsh is by using a the zprof module.

I added the zmodload zsh/zprof to the top of my .zshrc and zprof to the bottom of my .zshrc.

zmodload: a zsh command for loading optional zsh modules that aren’t loaded by default, since 99.999999% of times you don’t need ’em.<br>zsh/zprof: the profiling module; once loaded, it records the timestamps for every function call.<br>zprof: we need to call this at the bottom of .zshrc to dump the report to the stdio.

Boom! The biggest culprits – eager loading conda and nvm. Between the two, they took up around 2.2s of load time.

I replaced nvm with fnm and started lazy loading conda.

Next up, hardcoded brew --prefix to the actual path on my computer to shave off another ~60ms.

The last one was a hairy one.

1. fpath is a zsh array variable that lists directories where zsh looks for autoloadable functions, most importantly, completion definition files. It’s the zsh equivalent of PATH, but instead of finding executables, it finds function files.

2. compinit initializes zsh’s completion system, it’s what enables tab completion. Scans every directory in fpath looking for completion definition files (files named like _git, _npm, _docker, etc.). Builds a completion dump file (~/.zcompdump) that indexes all those completions. On subsequent runs, loads from that cached dump instead of rescanning everything.

3. If the cache is missed, it builds the cache and it can be expensive. I found that out the hard way.

In my case, every single time I opened a new terminal session brew kept adding the /opt/homebrew/share/zsh/site-functions to the fpath and compinit hashed the current fpath and compared it to the hash stored in .zcompdump. Because the hash didn’t match, it kept rebuilding it every time.

To fix this, I made the fpath a unique array, meaning all the dupes would be dropped anytime they get added to it. typeset -U fpath – this essentially solved my problem. This shaved another ~430ms off.

That’s it, thanks to Ghostty + the new session setup, my new sessions open in ~230ms, which is pretty good.

And, for the first time in over 14 years, I’ve upgraded my console prompt to something more nicer than the basic text. This is what it looks like:

Features

Directory focus

The path displayed only highlights the current working directly for better focus.

Error code display

I added a feature to display the error code from the last run command.

Execution timer

If a command takes longer than 4 seconds, the time taken to run the command is displayed on the right side of the terminal (you can see the 6s on in the screenshot).

The other features include the standard ones like git branch and dirty status. That’s it. It’s still quite lightweight in terms of the features, but I like it that way.

Anyway, I had a real good time chasing these issues down and fixing them.

Like this:<br>Like Loading…

profiling shell zsh

Comments

CommentsCancel reply

Discover more from Mohnish Thallavajhula

Subscribe now to keep reading and get access to the full archive.

Type your email…

Subscribe

Continue reading

Loading Comments...

Write a Comment...

Email (Required)

Name (Required)

Website

%d

time load from fpath like session

Related Articles