Zsh functions are like little scripts you can call from your command line, but "autoloading" is where it gets really clever – it means Zsh will only load a function into memory when you actually use it, saving precious startup time.

Let’s see this in action. Imagine you have a bunch of custom commands for managing your development projects. Instead of dumping them all into your .zshrc and slowing down your shell, you can use autoloading.

First, create a directory for your custom functions, say ~/.zsh/functions. Inside this directory, create a file for each function. For example, to create a project_status function, you’d make ~/.zsh/functions/project_status.

# ~/.zsh/functions/project_status
project_status() {
  echo "Checking status for project: $(basename $PWD)"
  git status -sb
  if [ -d .git/hooks ]; then
    echo "Git hooks are present."
  fi
}

Now, you need to tell Zsh where to find these functions. Add this to your .zshrc:

# ~/.zshrc
fpath=(~/.zsh/functions $fpath)
autoload -Uz project_status

fpath is an array of directories Zsh searches for functions. We’re prepending our custom directory to it. autoload -Uz function_name tells Zsh to lazily load function_name from any directory in fpath. The -U flag ensures that the function is loaded as a normal shell function, not as a special Zsh builtin. The -z flag is for Zsh’s security features, ensuring the function is safe to load.

Now, open a new Zsh session. If you type project_status, Zsh will look in ~/.zsh/functions, find the project_status file, load its content into memory, and then execute it.

% cd ~/my-awesome-project
% project_status
Checking status for project: my-awesome-project
## main...origin/main
Your branch is up to date with 'origin/main'.
no files changed
Git hooks are present.

You can even autoload multiple functions at once:

# ~/.zshrc
fpath=(~/.zsh/functions $fpath)
autoload -Uz project_status project_deploy project_cleanup

This setup is great for keeping your .zshrc lean and your shell lightning-fast, especially if you have dozens or even hundreds of custom functions. Zsh only loads what you use, when you use it.

The surprising part is how Zsh actually finds these functions. When you type a command that isn’t a built-in or an executable in your $PATH, Zsh iterates through the directories listed in $fpath. For each directory, it looks for a file with the same name as the command. If it finds one and the command was marked with autoload, Zsh will source that file, effectively turning its contents into a function definition, and then execute it. This whole process is transparent to you; it just feels like you typed a command.

One common pattern is to group related functions into a single file, often named after the group. For example, you might have ~/.zsh/functions/git_helpers containing git_commit_all, git_push_branch, etc. You’d then autoload -Uz git_helpers/* to autoload all functions within that file. Zsh is smart enough to parse this file and define each function individually.

When you have a very large number of functions, or functions that are computationally expensive to load (e.g., they do some setup or check), you might notice a slight delay the first time you call a specific function after starting Zsh. This is the autoloading mechanism in action – it’s reading the file and defining the function. Subsequent calls to that same function will be instantaneous because it’s already loaded.

The next hurdle you’ll likely encounter is managing complex function dependencies or ensuring functions are available in specific contexts, which leads into Zsh’s module system and more advanced autoloading configurations.

Want structured learning?

Take the full Zsh course →