Fixing ESLint and Prettier Fighting Each Other on Save in VS Code
Saving a file visibly flickers — quotes swap, a trailing comma appears and disappears, indentation shifts twice — and the file sometimes lands in a state neither tool would produce alone. It isn't a bug in either extension; it's two formatters with genuinely conflicting opinions both acting on the same save.
The Problem
Saving a file in VS Code visibly changes it twice in quick succession — quote style flips, a trailing comma appears then disappears, indentation shifts and then shifts back. Sometimes the file settles into a stable but unexpected state; other times consecutive saves oscillate between two different formattings entirely. It doesn't look like either extension is broken individually — each behaves reasonably in isolation — but together they clearly don't agree on what the file should look like.
Why It Happens
ESLint's own ruleset can include formatting rules that overlap with Prettier's opinions
ESLint isn't purely a correctness linter by default — depending on the configuration, its ruleset can include purely stylistic rules (indentation, quote style, semicolon usage) that duplicate exactly what Prettier is also trying to enforce. When both tools run on save and their formatting opinions genuinely differ, each one undoes part of what the other just applied — the visible "flicker" is literally two formatters actively contesting the same lines, with the final state depending on VS Code's specific ordering of format-on-save and code-action events.
eslint-config-prettier is missing, or present but not actually last in the config
The standard fix for this exact conflict is eslint-config-prettier, whose entire purpose is to disable every ESLint stylistic rule that could conflict with Prettier. A common misconfiguration is either never installing it, or installing it but not placing it last in ESLint's extends array — config resolution order matters, and anything listed after eslint-config-prettier in the array can silently re-enable a rule that config just turned off, leaving the conflict fully intact despite the package being present in package.json.
VS Code settings can let both tools claim the same file as their formatter
editor.defaultFormatter and ESLint's own code-action-on-save behavior (source.fixAll.eslint) can both be enabled simultaneously for the same language, and depending on which fires first, a file can get formatted by Prettier, then have ESLint's fixer run over it and change it again, in an order that isn't obviously visible just from reading the settings file.
The Fix
1. Install eslint-config-prettier and put it last in the extends array
// .eslintrc.json
{
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"prettier"
]
}
Order matters here specifically: "prettier" (from eslint-config-prettier) must be the last entry so it disables conflicting stylistic rules after every other config has had a chance to enable them — placing it earlier means a later config in the list can silently turn a disabled rule back on.
2. Give each tool a distinct job — Prettier owns formatting, ESLint owns correctness
Prettier should be the single source of truth for quote style, spacing, line length, and trailing commas; ESLint's role should be limited to correctness and logic rules — unused variables, undefined references, hook dependency arrays — that Prettier has no opinion about at all. Neither tool should be asked to have an opinion on the same formatting concern as the other.
3. Set the default formatter explicitly and scope ESLint's save behavior to non-formatting fixes
// .vscode/settings.json
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
With eslint-config-prettier correctly disabling ESLint's stylistic rules, source.fixAll.eslint on save is safe to leave enabled — it will only apply actual correctness fixes, not formatting ones, since the conflicting rules no longer exist to fix. Prettier remains the sole formatter, and the two no longer compete over the same lines.
4. Verify by running both CLIs directly against the same file, outside the editor
npx eslint --fix src/App.tsx
npx prettier --write src/App.tsx
git diff src/App.tsx
Running each tool's CLI in sequence and checking whether the file changes again after both have run confirms whether the underlying configuration actually agrees — if the CLI tools themselves produce a stable, unchanging result, the editor extensions will too, regardless of any remaining VS Code-specific settings; if the CLIs still disagree, the conflict is in the shared config, not in the editor.
Why This Works
Each fix removes one source of the same underlying conflict: two tools with genuinely overlapping opinions both acting on the same file. Correctly ordering eslint-config-prettier last ensures ESLint's conflicting stylistic rules are actually turned off rather than just nominally addressed; assigning each tool a distinct responsibility prevents the overlap from existing in the first place; explicit formatter and save-action settings remove ambiguity about which tool touches the file when; and verifying via CLI isolates whether the conflict lives in the shared configuration or in editor-specific settings.
Conclusion
ESLint and Prettier visibly fighting on save means their configurations genuinely disagree about formatting, not that either extension is malfunctioning — almost always because ESLint's own stylistic rules were never actually disabled for Prettier-covered concerns. Install eslint-config-prettier and ensure it's the last entry in ESLint's config, let Prettier own all formatting decisions while ESLint owns only correctness rules, set the default formatter explicitly rather than leaving both tools free to claim the same file, and confirm the fix by running both CLIs directly before trusting the editor's behavior.
