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

> Source: <https://iam.mt/bringing-down-my-zsh-load-times-from-3-1s-to-230ms/>
> Published: 2026-06-17 17:37:38+00:00

This year, the amount of time I spend in the [terminal](https://iam.mt/ghostty/) 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.`-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)`-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.`zsh/zprof`

: the profiling module; once loaded, it records the timestamps for every function call.`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](https://github.com/conda/conda) and

[. Between the two, they took up around 2.2s of load time.](https://github.com/nvm-sh/nvm)

`nvm`

I replaced nvm with [ fnm](https://github.com/Schniz/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.
