{"slug": "bringing-down-my-zsh-load-times-from-3-1s-to-230ms", "title": "Bringing down my ZSH load times from ~3.1s to ~230ms", "summary": "A developer reduced ZSH shell load times from 3.1 seconds to 230 milliseconds by profiling and optimizing their configuration. Key changes included replacing nvm with fnm, lazy loading conda, hardcoding brew --prefix, and fixing fpath duplication that caused compinit to rebuild its cache on every session.", "body_md": "This year, the amount of time I spend in the [terminal](https://iam.mt/ghostty/) has gone significantly up thanks to Claude Code.\n\nCouple of things:\n\n- I have this insane tendency to close tabs as soon as I’m done with them\n- Over the year, my work computer ZSH dotfiles have gotten extremely bloated\n\nI got to a point where I couldn’t tolerate how slow opening a new shell session got. Naturally, I decided to fix it.\n\nFirst, I needed to truly understand how slow my zsh was.\n\nI ran `time`\n\nto get the exact load time of my zsh.\n\n```\ntime zsh -il -c exit\n```\n\n`-i`\n\n: interactive mode; needed to understand prompts, completions, aliases, etc.`-l`\n\n: login mode; needed to load `/etc/zprofile`\n\nand `~/.zprofile`\n\n, etc.) in addition to `.zshrc`\n\n(I only use `.zshrc`\n\nand a `~/shell_overrides.sh`\n\nfile to load computer specific config)`-c exit`\n\n: run the exit command immediately and quit\n\nThese give you a true load time. The result didn’t come as a surprise.\n\n3.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.\n\nI wanted to get to that number. At least as close as I could.\n\nNext, 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.\n\nSo, I ran a profile. The way you do that in your zsh is by using a the `zprof`\n\nmodule.\n\nI added the `zmodload zsh/zprof`\n\nto the top of my `.zshrc`\n\nand `zprof`\n\nto the bottom of my `.zshrc`\n\n.\n\n`zmodload`\n\n: 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`\n\n: the profiling module; once loaded, it records the timestamps for every function call.`zprof`\n\n: we need to call this at the bottom of `.zshrc`\n\nto dump the report to the stdio.\n\nBoom! The biggest culprits – eager loading [ conda](https://github.com/conda/conda) and\n\n[. Between the two, they took up around 2.2s of load time.](https://github.com/nvm-sh/nvm)\n\n`nvm`\n\nI replaced nvm with [ fnm](https://github.com/Schniz/fnm) and started lazy loading\n\n`conda`\n\n.Next up, hardcoded `brew --prefix`\n\nto the actual path on my computer to shave off another ~60ms.\n\nThe last one was a hairy one.\n\n1. `fpath`\n\nis 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`\n\n, but instead of finding executables, it finds function files.\n\n2. `compinit`\n\ninitializes zsh’s completion system, it’s what enables tab completion. Scans every directory in `fpath`\n\nlooking for completion definition files (files named like `_git`\n\n, `_npm`\n\n, `_docker`\n\n, etc.). Builds a completion dump file (`~/.zcompdump`\n\n) that indexes all those completions. On subsequent runs, loads from that cached dump instead of rescanning everything.\n\n3. If the cache is missed, it builds the cache and it can be expensive. I found that out the hard way.\n\nIn my case, every single time I opened a new terminal session `brew`\n\nkept adding the `/opt/homebrew/share/zsh/site-functions`\n\nto the `fpath`\n\nand `compinit`\n\nhashed the current `fpath`\n\nand compared it to the hash stored in `.zcompdump`\n\n. Because the hash didn’t match, it kept rebuilding it every time.\n\nTo fix this, I made the `fpath`\n\na unique array, meaning all the dupes would be dropped anytime they get added to it. `typeset -U fpath`\n\n– this essentially solved my problem. This shaved another ~430ms off.\n\nThat’s it, thanks to Ghostty + the new session setup, my new sessions open in ~230ms, which is pretty good.\n\nAnd, 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:\n\n## Features\n\n### Directory focus\n\nThe path displayed only highlights the current working directly for better focus.\n\n### Error code display\n\nI added a feature to display the error code from the last run command.\n\n### Execution timer\n\nIf 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`\n\non in the screenshot).\n\nThe other features include the standard ones like `git`\n\nbranch and dirty status. That’s it. It’s still quite lightweight in terms of the features, but I like it that way.\n\nAnyway, I had a real good time chasing these issues down and fixing them.", "url": "https://wpnews.pro/news/bringing-down-my-zsh-load-times-from-3-1s-to-230ms", "canonical_source": "https://iam.mt/bringing-down-my-zsh-load-times-from-3-1s-to-230ms/", "published_at": "2026-06-17 17:37:38+00:00", "updated_at": "2026-06-17 17:53:28.763172+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["ZSH", "conda", "nvm", "fnm", "brew", "Ghostty", "Claude Code"], "alternates": {"html": "https://wpnews.pro/news/bringing-down-my-zsh-load-times-from-3-1s-to-230ms", "markdown": "https://wpnews.pro/news/bringing-down-my-zsh-load-times-from-3-1s-to-230ms.md", "text": "https://wpnews.pro/news/bringing-down-my-zsh-load-times-from-3-1s-to-230ms.txt", "jsonld": "https://wpnews.pro/news/bringing-down-my-zsh-load-times-from-3-1s-to-230ms.jsonld"}}