Environment Variables and PATH, Explained
What environment variables actually are, why they hold config and secrets, and how PATH decides which binary runs when you type a command.
Type node and the right program runs. Set DATABASE_URL and your app connects without a single credential in the source. Both of those are environment variables doing quiet work, and understanding them removes a whole category of confusing errors.
What an environment variable is
An environment variable is a named string the operating system attaches to a running process. Each process gets its own set, stored as simple KEY=value pairs — there are no numbers, booleans, or nested objects at this level, just text. Your program reads them through whatever its language exposes: process.env.HOME in Node, os.environ["HOME"] in Python, $HOME in a shell.
The detail that makes them useful is inheritance. When a process starts a child process, the child receives a copy of the parent’s environment. Your shell has an environment; every command you launch from it inherits that environment; anything those commands launch inherits it in turn. This is why exporting a variable in your terminal makes it visible to the programs you then run there — and why it vanishes when you close the terminal, since that process and its environment are gone.
A few names show up almost everywhere. HOME points at your home directory. USER (or USERNAME on Windows) holds your login name. SHELL names your default shell, PWD your current directory, and LANG your locale. You rarely set these yourself; the system populates them at login.
You set your own variables in a few standard ways. In a shell, export NAME=value makes the variable available to child processes for that session (plain NAME=value without export keeps it local to the shell itself). To persist it, add the export line to a startup file like ~/.bashrc or ~/.zshrc. For applications, a .env file holds project-specific values that a loader reads at startup. In CI and production, the same values are injected as pipeline secrets or platform-managed environment settings.
Why config and secrets live here
The reason this pattern is everywhere is separation. Your code describes behavior; the environment describes the situation it runs in — which database, which API key, which log level. Keeping those apart means the exact same build runs in development, staging, and production with nothing changed but the environment around it.
This is the idea behind the “config in the environment” rule popularized by the Twelve-Factor App methodology: anything that varies between deploys should live in the environment, not in the code. The practical payoff is concrete. Secrets stay out of version control, so a leaked repository does not leak your production keys. Rotating a credential is an environment change and a restart, not a code change and a redeploy. And there is no risk of a developer’s local database URL accidentally shipping to production, because the URL was never in the code to begin with.
PATH: how the shell finds commands
PATH is the single most consequential environment variable, and it works exactly like the others — it is just a string. Its value is a list of directories joined by colons on macOS and Linux (semicolons on Windows):
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin
When you type a bare command like git, the shell does not search your whole disk. It walks the directories in PATH from left to right and runs the first executable named git it finds. If it reaches the end of the list without a match, you get the familiar command not found. That error is rarely about a missing program — usually the binary exists but its directory is not on PATH. (You can ask the shell which file it would actually run with which git or, in many shells, type git.)
To make a new tool runnable, you add its directory to PATH. Prepending puts it ahead of everything else:
export PATH="$HOME/.local/bin:$PATH"
The order matters more than it looks.
FAQ
FAQ
Why does my variable disappear when I open a new terminal?
What is the difference between setting NAME=value and export NAME=value?
Are .env files automatically loaded by the operating system?
Related reading
2026-06-22
TCP vs UDP, Explained Through What Breaks When You Pick Wrong
TCP and UDP aren't interchangeable. We walk through the exact failure modes — head-of-line blocking, silent packet loss, Nagle delays — that show up when you pick the wrong transport.
2026-06-22
Write-Ahead Logging: How Databases Survive a Power Cut
How write-ahead logging keeps your data intact when the machine dies mid-write — the log-first rule, fsync, checkpoints, and why PostgreSQL and SQLite both rely on it.
2026-06-22
Backpressure, Explained Through a Queue That Won't Fall Over
What backpressure actually is, why an unbounded queue is a memory leak in disguise, and the four strategies a producer can take when a consumer falls behind.
2026-06-22
What a Bloom Filter Actually Saves You (and When It Lies)
A bloom filter trades a small false-positive rate for big memory savings. Here is the math behind the trade, where it pays off, and the failure mode that bites people.
2026-06-22
Idempotency, Explained Through the Retry That Doesn't Double-Charge
A practical look at idempotency keys: why a retried payment request shouldn't charge a card twice, how the pattern works, and where it quietly breaks in production.
Get the best tools, weekly
One email every Friday. No spam, unsubscribe anytime.