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.