The x Pattern: What npx, uvx, bunx, and pipx Actually Do

At some point, you typed this into your terminal:
npx create-react-app my-appProbably copied it from a README without thinking too hard about it. It worked, so you moved on. Fair enough. But there's a question hiding in plain sight: what is npx? Why not just npm? And why do Python tutorials now tell you to run uvx instead of pip install? What are bunx and pipx?
They all solve the same problem. I didn't realize this for an embarrassingly long time, so here's the explanation I wish I'd had earlier.
The Problem: Global Installs Are Broken
Say you want to use a CLI tool like create-react-app, eslint, or black (the Python formatter). The old approach was to install it globally:
npm install -g create-react-app
pip install blackThis puts the tool on your system PATH so you can run it from anywhere. Sounds reasonable until you actually do it for a while.
Version conflicts. Project A needs eslint@7. Project B needs eslint@8. You can only have one global version. Whichever you installed last wins, and the other project silently breaks.
System pollution. Every global install drops files into a shared directory. Over time, your global node_modules or Python site-packages becomes a graveyard of tools you installed once for a tutorial and never touched again. I ran pip list on a machine I'd been using for two years and found 140+ packages I couldn't account for.
Permission headaches. On many systems, global installs require sudo. Now you're running package managers as root, which is a security anti-pattern and a reliable way to corrupt your system Python.
Reproducibility. You send someone a README that says "first, install these five global tools." They install slightly different versions. Things break in ways that take hours to diagnose.
The "x" Pattern: Run It, Don't Install It
Every major package ecosystem independently landed on the same answer: a runner that downloads a package to a temporary location, executes it, and (optionally) cleans up afterward. Nothing touches your global installs.
The "x" in all of these stands for "execute."
npx= node package executebunx= bun executepipx= pip executeuvx= uv execute
The mental model:
You get the tool you need, at the version you need, for as long as you need it.
The Runners, One by One
npx (Node.js / npm)
The one that started it. Shipped with npm 5.2 in 2017. If you have Node.js, you already have it.
# Scaffold a new Next.js project
npx create-next-app@latest my-app
# Run a one-off tool without installing it
npx prettier --write "**/*.js"
# Run a specific version
npx eslint@8 .How it decides what to run:
- Checks if the package exists in your local
node_modules/.bin/ - If not, checks the global install
- If not found anywhere, downloads it temporarily from the npm registry, runs it, then caches it
This is why npx eslint . works differently depending on whether eslint is in your project's devDependencies or not. If it's local, npx just uses the local one. If not, it fetches it.
pnpm dlx (pnpm)
pnpm's equivalent of npx. It used to be called pnpx, but the current recommended command is pnpm dlx (short for "download and execute").
# Same as npx create-next-app
pnpm dlx create-next-app@latest my-app
# Run a one-off tool
pnpm dlx depcheckFunctionally identical to npx, but uses pnpm's content-addressable store. If you already have the package cached from a previous pnpm install, it reuses those files instead of downloading again. Slightly more disk-efficient.
bunx (Bun)
Bun's version. Same idea, but fast. Bun's package resolution is written in Zig and it shows.
# Same thing, noticeably quicker
bunx create-next-app@latest my-app
# Run TypeScript files directly (Bun handles TS natively)
bunx tsx my-script.tsIf cold-start speed matters to you, bunx is the one to reach for in the JS ecosystem.
pipx (Python)
pipx was the first mainstream "x" runner for Python (2019). It installs Python CLI tools into isolated virtual environments so they never collide with each other or with your system Python.
# Install a tool in an isolated environment
pipx install black
# Run something once without installing
pipx run cowsay "hello from an isolated environment"
# Run a specific version
pipx run --spec black==23.1.0 black --check .Key difference from the JS runners: pipx defaults to persistent isolated installs rather than temporary ones. pipx install black creates a dedicated virtualenv for black that sticks around. pipx run is the one-shot equivalent of npx.
This makes sense for the Python ecosystem, where CLI tools like black, ruff, mypy, and httpie tend to be things you use daily rather than one-off scaffolders.
uvx (Python / uv)
This is the one I reach for most these days. uvx is part of uv, the Rust-based Python package manager from Astral (the Ruff people). Drop-in replacement for pipx run.
# Same as pipx run, but much faster
uvx black --check .
# Run with a specific version
uvx ruff@0.3.0 check .
# Run a tool that comes from a different package name
uvx --from jupyter-core jupyterSpeed-wise, uv resolves and installs packages so quickly that uvx black . barely feels different from running a locally installed black. The cold-start tax that made pipx feel sluggish is basically gone.
Under the Hood: Where Do the Packages Go?
These runners aren't magic. They download real files to real directories. Knowing where is useful when something breaks or you want to clear the cache.
| Runner | Cache / Install Location |
|---|---|
| npx | ~/.npm/_npx/ (cached packages) |
| pnpm dlx | pnpm's content-addressable store (~/.local/share/pnpm/store/) |
| bunx | ~/.bun/install/cache/ |
| pipx | ~/.local/pipx/venvs/ (one virtualenv per tool) |
| uvx | ~/.local/share/uv/tools/ (for persistent installs) |
The JS runners tend toward ephemeral caching: download, run, keep it around in case you need it again. The Python runners lean toward persistent isolated environments. This split makes sense when you think about how each ecosystem uses CLI tools. JS devs scaffold projects occasionally. Python devs run formatters and linters daily.
When to Use a Runner vs. When to Install
The runner isn't always the right call. Here's how I think about it:
Use the runner when:
- You're scaffolding a new project (
npx create-next-app,uvx cookiecutter) - You need a tool once or rarely
- You want to try a tool before committing to it
- You're writing a README and don't want to add "first install these global dependencies" as a prerequisite
Install it properly when:
- It's a dev dependency for your project (put it in devDependencies / pyproject.toml)
- You use it every day (
black,ruff,eslintin your editor) - Speed matters on every invocation (runners have a small resolution overhead)
- You need reproducible versions locked in a config file
Convergent Evolution
The thing I find genuinely interesting here isn't any individual tool. It's the timeline.
JavaScript got npx in 2017. Python got pipx in 2019. Bun shipped bunx in 2023. uv added uvx in 2024. Four different ecosystems, four different teams, no coordination, same conclusion. When that happens in biology they call it convergent evolution. Same thing applies to tooling: if everyone independently arrives at the same answer, the underlying problem is probably real.
The old model was: install everything, manage it yourself, hope nothing conflicts. The new model is: tools are ephemeral by default and isolated when persistent. Your system is not a junk drawer.
If you're starting a new project today, check whether there's an "x" runner for your ecosystem. There almost certainly is.
Related Posts
Red/Green TDD with Coding Agents: Why Test-First Matters More
When AI writes your code, tests become the spec. Red/green TDD isn't just a practice anymore. It's the interface between intent and implementation.
Vibe Coding Is Real but Not What You Think
Everyone's talking about vibe coding. After years of using AI to write code, here's what it actually is, what it isn't, and why understanding the code still matters.
5 Python Libraries Every Data Science Student Should Know in 2023
The Python data science stack is evolving fast. Some sacred cows are being challenged, and your coursework might not cover the tools that actually matter.