In Homebrew on macOS, what is the difference between a formula and a cask, and where does each kind of package end up on disk?
answer
- two package types, one command
- built by Homebrew vs shipped by vendor
- Cellar and symlinks vs Caskroom and /Applications
- one of them updates itself behind brew's back
basics
~20 sHomebrew formulae are command-line packages that Homebrew builds or downloads as a prebuilt bottle into its own prefix and symlinks onto PATH. Casks are pre-built macOS applications and installers that Homebrew downloads and places in /Applications.
solid answer
~50 sHomebrew has two package types. A *formula* is a Ruby recipe for open-source software Homebrew can build itself — usually a command-line tool. It installs into a versioned directory under `$(brew --prefix)/Cellar/<name>/<version>` and is then symlinked into the prefix's `bin`, `lib` and `share`, which is how `jq` or `wget` appears on your PATH. In practice nothing compiles: Homebrew downloads a *bottle*, a prebuilt binary tarball matching your macOS version and CPU architecture. A *cask* describes an artifact the vendor already built — a `.app` inside a `.dmg` or `.zip`, a `.pkg` installer, a font or a driver. `brew install --cask firefox` stages it under the prefix's `Caskroom` and moves or links the application into `/Applications`. The practical consequences: casks are not built or versioned by Homebrew, so many of them update themselves and `brew upgrade` will not see it; and when a name exists as both, you must say `--cask` to disambiguate.
go deeper
Be able to say plainly that formulae are command-line packages Homebrew installs into its own prefix and casks are pre-built Mac applications that land in /Applications, and that casks need the --cask flag.
Explain the mechanics: Cellar keg plus symlinks into the prefix, bottles instead of local builds, Caskroom staging, and why a self-updating cask is invisible to brew upgrade.
Show you reason about where files actually land — resolve paths with brew --prefix rather than hardcoding, and know that keg-only formulae exist precisely so Homebrew does not shadow system libraries.
Frame the tradeoff: casks hand version control to the vendor, which is fine for a laptop browser and unacceptable for a build-critical tool, so decide deliberately which class of software a team installs through Homebrew at all.
## Two package types, one tool Homebrew is a third-party package manager for macOS. It is not part of the operating system and Apple ships nothing like it, which is why almost every macOS development machine has it. Homebrew manages two quite different kinds of thing, and interviewers ask about the distinction because confusing them is the first thing that trips a new macOS user. ## Formulae: software Homebrew can build A **formula** is a Ruby file that describes how to obtain and build a piece of software — its source URL, its checksum, its dependencies, and the build steps. Formulae are overwhelmingly open-source command-line software: `jq`, `wget`, `ripgrep`, `postgresql@16`, `[email protected]`. Installing a formula does not usually compile anything. Homebrew first looks for a **bottle**: a prebuilt binary tarball, built by Homebrew's own CI for a specific macOS version and CPU architecture and served from Homebrew's package registry. If a bottle exists for your platform it is downloaded and unpacked; if not, Homebrew falls back to building from source, which is when you discover you need Apple's Command Line Tools installed. The layout matters: ```bash brew --prefix # /opt/homebrew on Apple Silicon, /usr/local on Intel ls $(brew --prefix)/Cellar # one directory per installed formula ls -l $(brew --prefix)/bin/jq # a symlink into the Cellar ``` Each formula is unpacked into its own versioned directory — a *keg* — under `Cellar/<name>/<version>`, and then **linked**: Homebrew creates symlinks from the prefix's `bin`, `lib`, `include`, `share` and `share/man` into that keg. Because the prefix's `bin` is on your PATH, the command becomes available. This design is why uninstalling is clean (remove the keg, remove the symlinks) and why you can have several versions on disk while only one is linked. `brew link` and `brew unlink` flip that. Some formulae are **keg-only**: deliberately not symlinked into the prefix, because linking them would shadow a library or tool that macOS itself provides. Homebrew prints a caveat telling you the PATH or compiler-flag exports to use if you really want them, and other formulae that depend on them reference the keg path directly. ## Casks: software someone else built A **cask** describes an already-built macOS artifact: a `.app` bundle delivered in a `.dmg` or `.zip`, a `.pkg` installer, a font, a QuickLook plugin, a preference pane. Homebrew cannot build these — it downloads the vendor's file, checks it against the checksum in the cask definition, stages it under `Caskroom/<name>/<version>` in the prefix, and then moves or links the application into `/Applications`. ```bash brew install --cask firefox brew uninstall --cask firefox brew uninstall --cask --zap firefox # also remove leftover support files ``` Because the artifact is the vendor's binary, several things follow that surprise people: - **Many casks self-update.** A cask definition can declare that the app updates itself, and Homebrew then leaves it alone on `brew upgrade`; you need `brew upgrade --cask --greedy` to force those. So `brew outdated` is not a complete picture of what is stale on a Mac. - **Versions are the vendor's**, not Homebrew's. There is no build system to patch or pin against. - **`--zap` is not `uninstall`.** A plain uninstall removes the app; applications scatter preferences and support directories that only `--zap` cleans up. ## The name collision that catches people Some tokens exist as both a formula and a cask — `docker` is the classic example, where the formula is the command-line client and the cask is Docker Desktop, the full GUI application. Homebrew refuses to guess: `brew install --cask docker` and `brew install docker` do different things. `brew info <name>` tells you which type you are looking at, and `brew search` marks both lists. ## Why an interviewer cares The question is really "do you understand that Homebrew is doing two different jobs?" A candidate who thinks casks are just "formulae for GUI apps" will not understand why `brew upgrade` left an application three versions behind, why a cask cannot be built for a different architecture, or why `brew --prefix` is the right way to find a formula's files in a script while `/Applications` is where a cask's payload lives.
- Why does `brew upgrade` sometimes report nothing to do while an application installed as a cask is clearly out of date?A cask can declare that the application updates itself. For those, Homebrew deliberately skips the upgrade — the vendor's own updater owns the version, and Homebrew's recorded version would just drift. `brew upgrade --cask --greedy` overrides that and reinstalls from the cask definition regardless.
- What does it mean for a formula to be keg-only, and how do you use one?Keg-only means Homebrew installs it into the Cellar but does not symlink it into the prefix, because it would shadow something macOS already provides. It stays usable via its keg path: put `$(brew --prefix <name>)/bin` on PATH, or point compiler flags at `$(brew --prefix <name>)/include` and `/lib`. Dependent formulae reference the keg directly.
- If almost nothing is compiled locally, what is a bottle and when do you fall back to building from source?A bottle is a prebuilt binary tarball produced by Homebrew's CI for a specific macOS version and CPU architecture. If no bottle matches your platform — an old or brand-new macOS, an unusual prefix, or a formula built with non-default options — Homebrew builds from source, which requires Apple's Command Line Tools and takes far longer.
saying these in an interview costs you the question
- Thinking casks are simply formulae for GUI applications
- Believing brew upgrade keeps every installed cask current
- Assuming brew compiles everything from source on install
- Hardcoding /usr/local instead of asking brew --prefix
- Expecting uninstall to remove an app's preference files