Linter and Formatter: ESLint, Prettier, Biome, Ruff
Introduction
Code quality tools have converged into two categories: linters (find bugs and enforce style rules) and formatters (automatically format code). The traditional approach uses separate tools for each. Newer all-in-one tools combine both in a single binary. This article compares ESLint + Prettier, Biome, and Ruff.
ESLint + Prettier
The established standard for JavaScript/TypeScript:
// eslint.config.js (flat config — v9+)
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
plugins: { react },
rules: {
"react/jsx-key": "error",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"no-console": "warn",
"prefer-const": "error",
"no-var": "error",
},
ignores: ["dist/**", "node_modules/**"],
},
];
// .prettierrc
{
"semi": true,
"trailingComma": "all",
"singleQuote": false,
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "always",
"endOfLine": "lf"
}
# Check and fix
npx eslint src/
npx eslint src/ --fix
npx prettier --check src/
npx prettier --write src/
**Strengths**: Largest ecosystem with 1000s of plugins, extensive customization, well-documented rules, strong community.
**Weaknesses**: Two separate tools require coordination. ESLint configuration can be verbose. Performance can be slow on large codebases. Running both adds build time.
Biome
A Rust-based all-in-one linter and formatter for JS/TS/JSON/CSS:
// biome.json
{
"$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noBannedTypes": "error",
"noUselessConstructor": "error"
},
"correctness": {
"noUnusedVariables": "error",
"noUnusedImports": "error"
},
"style": {
"noNonNullAssertion": "warn",
"useConst": "error"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"formatWithErrors": false
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always",
"trailingComma": "all"
}
}
}
# Check and format
biome check src/
biome check src/ --apply
biome format src/
biome ci src/ # CI mode (strict)
# Lint only
biome lint src/
# Organize imports
biome check --formatter-enabled=true --linter-enabled=true
**Speed**: 10-50x faster than ESLint + Prettier. A 10,000-file TypeScript project checks in ~2 seconds vs ~60 seconds for ESLint.
**Strengths**: Single binary (Rust), unified configuration, dramatically faster, organizes imports automatically, fewer dependencies (no separate tools).
**Weaknesses**: Smaller ecosystem (fewer community rules), newer (some edge cases not covered), migration effort from ESLint.
Ruff
A Rust-based linter and formatter for Python:
# pyproject.toml
[tool.ruff]
target-version = "py311"
line-length = 100
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # bugbear
"SIM", # flake8-simplify
"ARG", # flake8-unused-arguments
]
ignore = [
"E501", # Line too long (handled by formatter)
]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "lf"
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401", "I001"]
"tests/**" = ["S101"] # Allow assert in tests
# Check and fix
ruff check src/
ruff check src/ --fix
ruff format src/ # Format only
ruff check src/ --watch # Watch mode
**Speed**: 10-100x faster than Flake8 + Black + isort combination. Ruff replaces dozens of linting plugins in a single binary.
**Strengths**: Drop-in replacement for Flake8, isort, pyupgrade, and many more. Extremely fast. Single configuration file. Active development with frequent releases.
**Weaknesses**: Some rules not yet implemented (Pylint plugin parity). Python only.
Comparison
| Feature | ESLint + Prettier | Biome | Ruff |
|---------|------------------|-------|------|
| Language | JS/TS | JS/TS/CSS/JSON | Python |
| Language | JavaScript/TypeScript | JavaScript/TypeScript/CSS | Python |
| Architecture | Node.js (JS) | Rust | Rust |
| Speed (10K files) | ~60s | ~2s | ~1s |
| Linter + Formatter | Separate tools | Single tool | Single tool |
| Plugin ecosystem | 1000s | Growing | Comprehensive |
| Configuration | Complex | Simple | Simple |
| Migrate from | N/A | `biome migrate eslint` | `ruff migrate` |
Recommendations
* **New JS/TS projects**: Start with Biome for the best developer experience and speed.
* **Existing ESLint + Prettier projects**: Migration to Biome is straightforward with `biome migrate eslint`. Otherwise, stick with the proven combination.
* **Python projects**: Ruff is the clear winner. It replaces Flake8, isort, Black, pyupgrade, and pylint in a single tool.
* **CI pipelines**: Biome and Ruff reduce CI lint times from minutes to seconds.
* **Pre-commit hooks**: All tools support staged-file-only checks with `--staged` flag.
The industry is clearly moving toward Rust-based all-in-one tools. Biome and Ruff represent the future of code quality tooling: fast, unified, and simple to configure.