The list that decides every command
$PATH is a colon-separated list of directories. The shell walks it left-to-right looking for executables. Everything you install adds to it; managing the order is what keeps multiple Pythons / Nodes / brews from fighting.
Append vs prepend
export PATH="$PATH:/new/dir"— append. New tool used only if a system one with the same name doesn't exist.export PATH="/new/dir:$PATH"— prepend. New tool wins. What you want for Homebrew, pyenv, mise, etc.
The Apple Silicon Homebrew dance
On Apple Silicon, Homebrew lives at /opt/homebrew/bin. Add this to ~/.zprofile:
eval "$(/opt/homebrew/bin/brew shellenv)"That one line sets PATH, MANPATH, INFOPATH, and a few brew vars in one atomic step. The official install script writes it for you on a fresh Mac.
Deduplicate
Sourcing the same file twice can repeat entries. zsh has a typeset -U path declaration that auto-deduplicates. After typeset -U path, every PATH addition is unique by string.
Inspect
echo $PATH | tr ':' '\n'
echo $PATH | tr ':' '\n' | sort | uniq -d # any duplicates?
type python3 # which one wins?Per-project PATH overrides
direnv (next-but-one lesson) lets a project-local .envrc insert its own bin dir at the front of PATH for that directory only. Combined with mise (modern-tools), each project can ship its toolchain without polluting the global env.
PATH is a search policy, not just a string
An early directory can shadow a system binary, and some implementations treat an empty entry as the current directory. After editing PATH, inspect the winner and alternatives with command -v and type -a.
Give one layer ownership of a project's runtime
If Conda, mise, pyenv, and Homebrew all rewrite PATH, activation order chooses the interpreter. Declare the manager owned by the repository and service, and keep other layers outside that boundary.