Ruff: A Complete Guide to Python's Fastest Linter and Formatter
Ruff is a Python linter and code formatter that replaces flake8, Black, isort, pyupgrade, pydocstyle, and dozens of other code quality tools with a single binary. Built by Astral (the team behind uv and ty), it runs 10-100x faster than the Python tools it replaces and re-implements over 800 lint rules from nearly 50 flake8 plugins and other tools.
Why Ruff exists
Ruff gives Python developers a single tool for linting, formatting, import sorting, and code modernization. One dev dependency, one configuration section in pyproject.toml, one CI step. It ships as a standalone binary with no runtime dependencies, so installation is fast and there are no version conflicts to manage.
The speed matters in practice. Ruff processes files in parallel and caches results aggressively, finishing in milliseconds on codebases where traditional tools take seconds or minutes. That speed makes it feasible to run the full lint and format suite on every save, every commit, and every CI push without slowing anyone down.
Before Ruff, achieving the same coverage meant combining flake8 for linting, Black for formatting, isort for imports, pyupgrade for modernization, and pydocstyle for docstrings. Each had its own config format and plugin ecosystem. Ruff re-implements the most useful rules from all of them, so teams can drop five or six tools in favor of one.
Installation
The recommended way to add Ruff to a project is as a dev dependency:
uv add --dev ruffFor one-off usage without installing:
uvx ruff check .For a global install available on your PATH:
uv tool install ruffAlternative methods:
pip install ruff
pipx install ruffConfirm the installation:
ruff --versionFor a guided walkthrough of setting up Ruff on a project, see the tutorial Set up Ruff for formatting and checking your code.
Core workflows
Linting with ruff check
ruff check scans Python files for errors, style violations, and potential bugs:
# Check all files in the current directory
ruff check .
# Check a specific file
ruff check src/main.py
# Check a specific directory
ruff check src/Output shows the file, line, column, rule code, and message:
src/main.py:3:1: F401 [*] `os` imported but unused
src/main.py:7:5: F841 Local variable `x` is assigned to but never usedThe [*] marker indicates a rule with an available auto-fix. Apply all safe fixes with:
ruff check --fix .Some fixes are marked “unsafe” because they may change program behavior (for example, removing an unused import that has side effects). Apply those too with:
ruff check --fix --unsafe-fixes .Warning
Review unsafe fixes before committing. An unused import that triggers module-level side effects will change program behavior if removed.
Formatting with ruff format
ruff format is designed as a replacement for Black. It produces nearly identical output on the vast majority of code, though there are known deviations in edge cases like end-of-line comment placement and trailing commas:
# Format all Python files
ruff format .
# Check formatting without changing files
ruff format --check .
# Show a diff of what would change
ruff format --diff .Note
uv format delegates to Ruff’s formatter under the hood. If you use uv, you can run uv format instead of ruff format without any additional setup.
Sorting imports
Import sorting in Ruff is a lint rule (I), not a separate command. To sort imports across your project:
ruff check --select I --fix .This replaces isort. The sorting behavior is configurable under [tool.ruff.lint.isort] in your pyproject.toml. For a focused walkthrough, see How to sort Python imports with Ruff.
Modernizing code
The pyupgrade rules (UP) automatically rewrite old patterns to use newer Python syntax. With UP enabled, ruff check --fix will convert old-style string formatting to f-strings, replace legacy type hints with modern syntax, remove unnecessary utf-8 encoding declarations, and apply dozens of other modernizations.
Configuration
All Ruff configuration lives in pyproject.toml under three sections. You can also use a standalone ruff.toml or .ruff.toml file:
[tool.ruff]
line-length = 88
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "I", "UP"]
[tool.ruff.format]
quote-style = "double"Tip
Ruff’s defaults (Pyflakes F and a subset of pycodestyle E) catch syntax errors and common bugs. Most projects will want to enable additional rule categories for style, imports, and modernization.
Selecting rules: select sets the full list of active rule categories. The Ruff docs recommend using select to make your rule set explicit. extend-select adds rules on top of the defaults without replacing them:
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]Ignoring rules: Disable specific rules with ignore:
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]
ignore = ["E501"] # Let the formatter handle line lengthPer-file overrides: Apply different rules to different parts of your codebase:
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"] # Allow assert in tests
"__init__.py" = ["F401"] # Allow unused imports in __init__For a curated set of rules to enable on new projects, see How to configure recommended Ruff defaults.
Suppressing rules
Three levels of suppression are available:
Line-level: Add # noqa: CODE to suppress a specific rule on one line:
x = 1 # noqa: F841Block-level: Use # ruff: disable[CODE] and # ruff: enable[CODE] to suppress rules across a range of lines:
# ruff: disable[E501]
LONG_VALUE = "a string that exceeds the line length limit on purpose"
# ruff: enable[E501]File-level: Add # ruff: noqa: CODE near the top of a file to suppress a rule for the entire file:
# ruff: noqa: F401How Ruff compares to alternatives
| Feature | Ruff | flake8 | Black | Pylint | isort |
|---|---|---|---|---|---|
| Linting | Yes | Yes | No | Yes | No |
| Formatting | Yes | No | Yes | No | No |
| Import sorting | Yes | Via plugin | No | No | Yes |
| Auto-fix | Yes | No | N/A | No | Yes |
| Speed | 10-100x faster | Baseline | Baseline | Slower | Baseline |
| Plugin system | No (built-in rules) | Yes | No | Yes | No |
| Config format | pyproject.toml | .flake8 / setup.cfg | pyproject.toml | pylintrc / pyproject.toml | pyproject.toml |
For an in-depth look at how Ruff and Pylint differ in rule coverage and analysis depth, see How do Ruff and Pylint compare?.
When to use Ruff
New projects: Enable Ruff from day one. A single dev dependency replaces three to five separate tools, and starting with a comprehensive rule set is easier than adding rules to an existing codebase later.
Existing flake8/Black/isort projects: Migration is straightforward. Most flake8 rule codes (e.g., E501, F401) map directly to Ruff rules, and the formatter produces output close to Black’s.
Large codebases: Ruff’s speed advantage makes the biggest difference in CI, where linting and formatting checks run on every push. What took 30 seconds with flake8 and Black often finishes in under a second with Ruff.
Important
When to pair with other tools: Pylint still offers some deeper semantic checks that Ruff has not yet re-implemented, though the gap narrows with each release.
Editor integration
VS Code and Cursor: Install the official ruff extension (charliermarsh.ruff). It provides linting, formatting, and code actions directly in the editor.
Neovim: Configure via nvim-lspconfig or Neovim’s built-in LSP client, pointing at ruff server.
PyCharm: Install the Ruff plugin from the JetBrains Marketplace.
Other editors: Ruff includes a built-in language server (ruff server) that speaks LSP. Any editor with LSP support can connect to it for real-time linting, formatting, and quick-fix suggestions.
Pre-commit integration
Ruff provides official pre-commit hooks for both linting and formatting:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.6
hooks:
- id: ruff
args: [--fix]
- id: ruff-formatThis runs ruff check --fix and ruff format on staged files before each commit.
Using Ruff with uv and ty
Ruff, uv, and ty form Astral’s Python toolchain. All three are single Rust binaries, configure through pyproject.toml, and work together without conflict.
A typical pyproject.toml using all three:
[tool.ruff]
line-length = 88
target-version = "py312"
[tool.ruff.lint]
extend-select = ["E", "F", "I", "UP", "B", "SIM"]
[tool.ruff.format]
quote-style = "double"
[tool.ty.rules]
unresolved-import = "warn"In a uv-managed project:
# Lint
uv run ruff check .
# Format (either works)
uv run ruff format .
uv format
# Type check
uv run ty checkMigrating from existing tools
Note
Ruff only reads configuration from pyproject.toml, ruff.toml, or .ruff.toml. If you have settings in .flake8, setup.cfg, or other legacy config files, you’ll need to move them.
From flake8: ruff check replaces flake8. Most flake8 rule codes (e.g., E501, F401) map directly to Ruff rules.
From Black: ruff format produces the same output as Black in the vast majority of cases. Copy your [tool.black] settings (like line-length) to [tool.ruff] and [tool.ruff.format]. There are known deviations in edge cases, but over 99.9% of lines are formatted identically.
From isort: Enable the I rules in Ruff. isort configuration options map to [tool.ruff.lint.isort]. See How to sort Python imports with Ruff.
From Pylint: Ruff covers a subset of Pylint’s rules. Check the Ruff rules reference to see which Pylint rules have Ruff equivalents (PLC, PLE, PLR, PLW prefixes). For rules Ruff doesn’t cover, you can run both tools. See How do Ruff and Pylint compare?.
Troubleshooting
Rule conflicts between linting and formatting: The ISC001 rule (implicit string concatenation) can conflict with Ruff’s formatter. If you see the formatter and linter fighting over the same line, disable ISC001:
[tool.ruff.lint]
extend-select = ["ISC"]
ignore = ["ISC001"]noqa comments not working: Ruff requires the exact rule code. A bare # noqa without a code will suppress all rules on that line, but # noqa: E501 only suppresses E501. Check that the rule code matches what Ruff reports in its output.
Rules not being selected: Remember that select replaces the default rule set, while extend-select adds to it. If you use select = ["I"], only import sorting rules run. Use extend-select = ["I"] to keep the defaults and add import sorting.
Learn more
Tutorials
How-to guides
- How to configure recommended Ruff defaults
- How to sort Python imports with Ruff
- How to set up pre-commit hooks for a Python project
Explanations
Reference
Get Python tooling updates
Subscribe to the newsletter