Formatters
Formatting is a solved problem. The only wrong answer is “we discuss it in review”.
Ruff format is the default
Ruff replaced Black as the standard in 2026. It’s Black-compatible in output, written in Rust, and typically far faster — and since Ruff is already your linter, it’s one tool and one config.
# pyproject.toml - the single config file for the whole toolchain
[tool.ruff]
line-length = 100
target-version = "py314"
[tool.ruff.format]
quote-style = "double"
ruff format . # format
ruff format --check . # CI gate
Black is still fine and still maintained; Ruff’s advantage is speed and consolidation, not output quality. If a codebase is on Black, migrating is optional.
Why opinionated formatters won
The argument that settled it: formatting debates cost more than any formatting choice. An automatic formatter with few options removes an entire category of review comment, keeps diffs minimal and meaningful, and eliminates style drift between contributors.
The corollary is to resist configuring it. Every option you set is a decision someone can reopen later. Line length is the only one most teams genuinely need.
Running it
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.0
hooks:
- id: ruff-format
- id: ruff
args: [--fix]
Three layers, and you want all of them:
- Editor on save — instant feedback, zero friction.
- Pre-commit hook — catches anyone whose editor isn’t configured.
- CI check — the enforcement that actually matters, since hooks can be bypassed.
CI should run --check and fail, not reformat and push. A bot committing to your branch is worse than a red build.
Adopting it on an existing codebase
Reformatting everything creates one enormous commit that ruins git blame.
ruff format .
git commit -m "style: apply ruff format"
git rev-parse HEAD >> .git-blame-ignore-revs
git config blame.ignoreRevsFile .git-blame-ignore-revs
git blame then skips the formatting commit and shows the real author. Do the reformat as its own commit with no logic changes, so review is trivial.
Other languages
| Language | Formatter |
|---|---|
| Python | Ruff format (or Black) |
| JS/TS | Prettier, or Biome for speed |
| Go | gofmt — the original argument for zero config |
| Rust | rustfmt |
| SQL | sqlfluff |
| Markdown, YAML, JSON | Prettier |
gofmt is worth naming: Go shipped one formatter with no options at all, and the complete absence of formatting debate in that ecosystem is the strongest evidence for the approach.
Interview angle
- “What formatter do you use and why?” — Ruff format: Black-compatible output, far faster, and the same tool as the linter so there’s one config in
pyproject.toml. Black remains fine; the win is consolidation. - “Why use an opinionated formatter at all?” — it removes a whole category of review comment, keeps diffs minimal, and stops style drift. The debate costs more than any particular choice, which is why
gofmtshipping with zero options was the right call. - “Where do you enforce it?” — editor on save, pre-commit hook, and a CI
--checkgate. CI is the one that counts, because hooks are bypassable. CI should fail rather than auto-commit. - “How do you introduce it to a large codebase without destroying blame?” — one formatting-only commit, then record its SHA in
.git-blame-ignore-revssogit blameskips it.