# Ruff: A Complete Guide to Python's Fastest Linter and Formatter


[Ruff](https://pydevtools.com/handbook/reference/ruff.md) is a Python linter and code formatter that bundles the jobs of flake8, [Black](https://pydevtools.com/handbook/reference/black.md), isort, pyupgrade, and pydocstyle into a single standalone binary. It re-implements more than 900 lint rules drawn from over 50 existing tools and runs 10-100x faster than the tools it replaces.

[OpenAI](https://astral.sh) (the team behind [uv](https://pydevtools.com/handbook/reference/uv.md) and [ty](https://pydevtools.com/handbook/reference/ty.md)) builds and maintains Ruff. It is open source under the MIT license and free for any project.

## 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](https://pydevtools.com/handbook/reference/pyproject.toml.md), 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.

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](https://pydevtools.com/handbook/reference/flake8.md) for linting, [Black](https://pydevtools.com/handbook/reference/black.md) 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:

```bash
uv add --dev ruff
```

For one-off usage without installing:

```bash
uvx ruff check .
```

For a global install available on your PATH:

```bash
uv tool install ruff
```

Alternative methods:

```bash
pip install ruff
pipx install ruff
```

Confirm the installation:

```bash
ruff --version
```

For a guided walkthrough of setting up Ruff on a project, see the tutorial [Set up Ruff for formatting and checking your code](https://pydevtools.com/handbook/tutorial/set-up-ruff-for-formatting-and-checking-your-code.md).

## Core workflows

### Linting with `ruff check`

`ruff check` scans Python files for errors, style violations, and potential bugs:

```bash
# 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 used
```

The `[*]` marker indicates a rule with an available auto-fix. Apply all safe fixes with:

```bash
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:

```bash
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](https://docs.astral.sh/ruff/formatter/black/) in edge cases like end-of-line comment placement and trailing commas:

```bash
# 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](https://pydevtools.com/handbook/reference/uv.md), 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:

```bash
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](https://pydevtools.com/handbook/how-to/how-to-sort-python-imports-with-ruff.md).

### 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.

The refurb rules (`FURB`) go a step further, suggesting more idiomatic rewrites: replacing set-add loops with `set.update()`, using `str.removeprefix()` instead of hand-rolled slicing, rewriting conditional expressions as `min()`/`max()` calls, and replacing `open()`/`read()` with `Path.read_text()`. All FURB rules include auto-fix support. Together, `UP` and `FURB` handle both syntax modernization and idiomatic pattern upgrades.

## Configuration

Ruff's rules are grouped into categories by source tool, each identified by a short prefix: `E`/`W` (pycodestyle), `F` (Pyflakes), `I` (isort), `UP` (pyupgrade), `B` (flake8-bugbear), `SIM` (flake8-simplify), `S` (flake8-bandit), `PL` (Pylint), and dozens more. Rules are selected by prefix, so `select = ["E"]` turns on every pycodestyle rule. Roughly half of the catalog ships an auto-fix.

All Ruff configuration lives in [pyproject.toml](https://pydevtools.com/handbook/reference/pyproject.toml.md) under three sections. You can also use a standalone `ruff.toml` or `.ruff.toml` file:

```toml
[tool.ruff]
line-length = 88
target-version = "py313"

[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:

```toml
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]
```

Ignoring rules: Disable specific rules with `ignore`:

```toml
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]
ignore = ["E501"]  # Let the formatter handle line length
```

Per-file overrides: Apply different rules to different parts of your codebase:

```toml
[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](https://pydevtools.com/handbook/how-to/how-to-configure-recommended-ruff-defaults.md).

## Suppressing rules

Three levels of suppression are available:

Line-level: Add `# noqa: CODE` to suppress a specific rule on one line:

```python
x = 1  # noqa: F841
```

Block-level: Use `# ruff: disable[CODE]` and `# ruff: enable[CODE]` to suppress rules across a range of lines:

```python
# 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:

```python
# ruff: noqa: F401
```

## How Ruff compares to alternatives

| Feature | Ruff | flake8 | [Black](https://pydevtools.com/handbook/reference/black.md) | [Pylint](https://pydevtools.com/handbook/reference/pylint.md) | 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?](https://pydevtools.com/handbook/explanation/how-do-ruff-and-pylint-compare.md).

## 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]
> Ruff handles style, common bugs, and code modernization, but it does not perform type analysis. Pair it with a type checker like [mypy](https://pydevtools.com/handbook/reference/mypy.md), [pyright](https://pydevtools.com/handbook/reference/pyright.md), or [ty](https://pydevtools.com/handbook/reference/ty.md) for type safety.

When to pair with other tools: [Pylint](https://pydevtools.com/handbook/reference/pylint.md) 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](https://pydevtools.com/handbook/how-to/how-to-set-up-pre-commit-hooks-for-a-python-project.md) hooks for both linting and formatting:

```yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.15
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format
```

This runs `ruff check --fix` and `ruff format` on staged files before each commit.

## Using Ruff with uv and ty

Ruff, [uv](https://pydevtools.com/handbook/reference/uv.md), and [ty](https://pydevtools.com/handbook/reference/ty.md) form OpenAI'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:

```toml
[tool.ruff]
line-length = 88
target-version = "py313"

[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:

```bash
# Lint
uv run ruff check .

# Format (either works)
uv run ruff format .
uv format

# Type check
uv run ty check
```

## Migrating 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](https://docs.astral.sh/ruff/formatter/black/) 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](https://pydevtools.com/handbook/how-to/how-to-sort-python-imports-with-ruff.md).

From Pylint: Ruff covers a subset of Pylint's rules. Check the [Ruff rules reference](https://docs.astral.sh/ruff/rules/) 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?](https://pydevtools.com/handbook/explanation/how-do-ruff-and-pylint-compare.md).

## 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`:

```toml
[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
- [Set up Ruff for formatting and checking your code](https://pydevtools.com/handbook/tutorial/set-up-ruff-for-formatting-and-checking-your-code.md)

### How-to guides
- [How to configure recommended Ruff defaults](https://pydevtools.com/handbook/how-to/how-to-configure-recommended-ruff-defaults.md)
- [How to sort Python imports with Ruff](https://pydevtools.com/handbook/how-to/how-to-sort-python-imports-with-ruff.md)
- [How to migrate from Black to Ruff formatter](https://pydevtools.com/handbook/how-to/how-to-migrate-from-black-to-ruff-formatter.md)
- [How to replace Black, isort, flake8, and pyupgrade with Ruff](https://pydevtools.com/handbook/how-to/how-to-replace-black-isort-flake8-pyupgrade-with-ruff.md)
- [How to enable Ruff security rules](https://pydevtools.com/handbook/how-to/how-to-enable-ruff-security-rules.md)
- [How to configure Ruff for Django](https://pydevtools.com/handbook/how-to/how-to-configure-ruff-for-django.md)
- [How to set up pre-commit hooks for a Python project](https://pydevtools.com/handbook/how-to/how-to-set-up-pre-commit-hooks-for-a-python-project.md)

### Explanations
- [How do Ruff and Pylint compare?](https://pydevtools.com/handbook/explanation/how-do-ruff-and-pylint-compare.md)
- [What is PEP 8?](https://pydevtools.com/handbook/explanation/what-is-pep-8.md)

### Reference
- [Ruff reference page](https://pydevtools.com/handbook/reference/ruff.md)
- [Ruff official documentation](https://docs.astral.sh/ruff/)
- [Ruff rules reference](https://docs.astral.sh/ruff/rules/)
