The difference, in one table
Both tools read your code without running it and both can change it. That is where the similarity stops. A linter has an opinion about whether your code is likely to be wrong. A formatter has no opinion about correctness at all, and only decides where things go on the page.
| Linter | Formatter | |
|---|---|---|
| Question it answers | Is this likely to be a mistake? | Where should this go on the page? |
| Typical tool | ESLint, Ruff, golangci-lint, Clippy | Prettier, dprint, gofmt, Black |
| Understands | Patterns and rules, one at a time | The whole file, all at once |
| Output when unhappy | A list of findings you decide about | A rewritten file |
| Configuration | Hundreds of rules, each switchable | Few options, deliberately |
| Can it be wrong? | Yes, false positives are normal | No, only disagreeable |
| Argument it starts | Which rules do we want? | None, once you have picked it |
The last row is the honest reason most teams end up with both. A formatter ends style arguments by removing the thing people argue about, and a linter is worth having precisely because it does raise things worth arguing about.
What the gap looks like on a real codebase
This site runs a linter and no formatter. That is an accident of how it was built, and it makes the repository a useful test: the linter is green, so anything a formatter would still change is the part of the job the linter was never doing.
We ran both against it on 23 August 2026 and recorded the output rather than describing it. ESLint 8.57.1, configured through eslint-config-next, resolves 80 rules, of which 71 are switched on: 56 declared at error level and 15 at warning, though we run it so that a warning fails the build too. It checked 371 TypeScript files in 5.5 seconds and reported no warnings and no errors.
Then Prettier 3.4.2, in check mode so it could not write anything, looked at the same 371 files and found 255 of them formatted differently from what it would produce. That is 69% of a codebase that its linter considers entirely clean.
| Directory | Files | Would change | Share |
|---|---|---|---|
| app/ | 121 | 71 | 59% |
| lib/ | 122 | 89 | 73% |
| components/ | 92 | 68 | 74% |
| tests/ | 36 | 27 | 75% |
| Total | 371 | 255 | 69% |
Two tools disagreeing about 69% of a codebase is not either of them malfunctioning. They were asked different questions, and only one of those questions had been asked here at all.
The number is deliberately unflattering to us. It is also the clearest way to show what a formatter is for: none of those 255 files contains a bug that the formatter found, and none of them would have been flagged by adding more lint rules.
ESLint stopped doing this job on purpose
For years the confusion was reasonable, because ESLint really did both. It shipped rules for indentation, quotes, semicolons and spacing alongside the rules about likely bugs, so you could run one tool and get both jobs done badly.
That ended in November 2023. ESLint deprecated all of its formatting rules in version 8.53.0, and the announcement is unusually direct about it: "We recommend using a source code formatter instead of ESLint for formatting your code." The team listed rule clashes, unrealistic expectations and effort against value among the reasons, and concluded that the approach would not scale further.
The reasoning matters more than the version number. A linter evaluates rules one at a time against patterns it recognises, so two formatting rules can each be satisfiable and still contradict each other in one file. A formatter parses the whole file and prints it back out, so it cannot contradict itself. Those are different architectures, not different amounts of effort.
The deprecated rules still exist, moved to a community plugin, so nothing broke for anyone who ignored the advice. But the direction is settled, and it dates the argument: if you are choosing between a linter and a formatter for layout, the linter's own maintainers already answered.
Our repository runs ESLint 8.57.1, which is after that deprecation. So the linter we measured had already stopped trying to format, and the 255 files are what that leaves behind.
What only a linter catches
A formatter will never tell you that your code is wrong, because it does not know what wrong means. Everything in this list is invisible to it:
- A React hook called conditionally, which breaks the rules of hooks and produces bugs that only appear on re-render. Our config has react-hooks switched on for exactly this.
- An await inside a loop that should have been a Promise.all, turning a fast operation into a slow one.
- A variable assigned and never read, which is usually a rename that only got applied in one place.
- An image without alt text, or a click handler on a div with no keyboard equivalent. Our config runs jsx-a11y, so these fail the build rather than reaching a user.
- An import from a path that does not resolve, caught before the build rather than during it.
- A comparison that will always be true because of how the two sides coerce.
None of these are style. Each one is a claim that the code will behave differently from how it reads, and that is the category a formatter has no access to.
What only a formatter settles
A linter can enforce a style rule, but it enforces it as a complaint. You get a list of violations and a decision to make about each one. A formatter does not complain, it just prints the file the same way every time, which produces two things a linter cannot.
The first is that diffs get smaller and more honest. When formatting is mechanical, every line in a pull request changed because someone changed it, not because their editor wrapped an argument list differently. Reviewers stop scrolling past noise, which is the difference between a review that catches something and one that gets approved because it was long.
The second is that the discussion stops. A formatter with few options is not being inflexible for its own sake. Every option it declines to add is a decision nobody on your team has to have an opinion about, and style opinions are close to free to hold and expensive to resolve.
This is also why running a formatter on an existing codebase feels alarming and is usually fine. The commit is enormous and touches almost everything, and it contains no behaviour change at all. Do it as its own commit, on its own, and never mixed into a change that does something.
Do you need both?
For most teams writing JavaScript or TypeScript, yes, and the split is clean once the formatting rules are out of the linter: the formatter owns layout, the linter owns correctness, and they no longer have anything to argue about.
There are honest exceptions. Some languages ship a formatter in the toolchain and settle the question by default, which is why Go code looks the same everywhere and gofmt is not a decision anyone makes. Some very small projects genuinely do not need either. In our experience the second contributor is usually where it starts paying.
And a single developer working alone gets less from a formatter, because the value is largely in not having the argument. That is the case this repository has been in, which is how it accumulated 255 files of drift without anything going wrong.
How to run them without them fighting
The historic advice was to install a compatibility package that switched off ESLint's formatting rules so Prettier could own them. Since 8.53.0 those rules are deprecated anyway, so on a current setup there is usually nothing to disable and the two tools coexist without configuration.
- Add the formatter and commit the reformat as its own commit, touching nothing else. Expect it to be large.
- Run the formatter on save in the editor, so nobody thinks about it again.
- Run both in CI: the formatter in check mode so it reports rather than writes, and the linter as a gate.
- Keep the formatter's config nearly empty. Every option you set is one you will later have to defend.
- Turn lint rules on one at a time, with the reason. A config someone copied and nobody understands gets switched off the first time it is inconvenient.
Check rather than write in CI. A pipeline that reformats and commits on your behalf will eventually do it to a branch someone is working on, and a tool that edits code without being asked loses the trust it needs to stay switched on.
What we actually run, and what it costs us
We run ESLint through eslint-config-next with the React, hooks, import and accessibility plugins on, as a hard gate in CI. We do not run a formatter, and the measurement above is what that has cost: 69% of the codebase is formatted to whatever each file's author settled on that day.
Nothing has broken because of it, which is the point. The cost is not correctness, it is that a chunk of every diff is layout rather than change, and that is a review tax paid continuously and invisibly. We are publishing the number rather than quietly fixing it first, because a comparison written by someone with a clean repository is easy to write and worth less.
A script produces these figures and writes them to a file in the repository, and they were then copied onto this page by hand. So if the two ever disagree, the recorded run is right and this page is stale. Re-run it rather than arguing with it.
What this does not cover
- The 69% figure describes one repository, ours. A codebase that already runs a formatter would report close to zero, and that would say nothing about either tool.
- It counts files that differ, not lines. A file differing by one line of indentation counts the same as one that would be rewritten throughout.
- The rule counts come from the ESLint config as resolved for a single representative file. A project with per-directory overrides would resolve differently.
- A clean lint run is a statement about the rules that are switched on, not about the code being correct. A linter cannot find a bug it has no rule for, and most bugs have no rule.
- This compares the two categories of tool using the JavaScript and TypeScript ecosystem. The split holds in most languages, but the specific tools and defaults do not transfer.
- We have not measured whether adding a formatter here would reduce review time. That would need a before and after we do not have, so the argument about diff noise is reasoning, not a finding.
- The figures were copied onto this page by hand from the recorded run. The run is the authority, not this page.
Sources
- Deprecating formatting rules, ESLint. Retrieved 23 August 2026.
- The whatscene.in codebase, 371 TypeScript files under app, lib, components and tests. ESLint 8.57.1 via npm run lint, and prettier 3.4.2 with --list-different so nothing could be written. Rule counts read from the resolved ESLint config for a representative file. Run: scripts/lint-evidence.mjs at commit 3970303, 2026-08-23.
Revisions
- 23 August 2026 First published, with the linter and formatter run recorded against this repository.
This page is revised in place rather than replaced, so its address does not change.