Skip to content

ruff — swiss knife for formatting & linting

Mental Model

Ruff is best understood as:

  • A rule engine, not “a linter”
  • A unification layer over many historical Python tools
  • A fast, enforceable policy mechanism for code quality

Ruff’s real value is not individual rules — it is centralized, opinionated configuration with negligible runtime cost.

If Flake8 asked:

“Which plugins do you want?”

Ruff asks:

“Which categories of mistakes do you want to forbid?”

What Ruff Replaces (Conceptually)

Ruff reimplements rules from:

  • pyflakes (logic errors)
  • pycodestyle (PEP8)
  • isort (imports)
  • mccabe (complexity)
  • many flake8 plugins

Key idea:

  • Ruff flattens a fragmented ecosystem into one rule namespace

Ruff Rule Taxonomy

Ruff rules are grouped by prefix, each representing a domain.

Examples:

  • F — pyflakes (logic errors)
  • E, W — style (pycodestyle)
  • I — import sorting
  • C90 — complexity
  • B — bugbear (likely bugs)
  • UP — Python upgrade suggestions
  • SIM — simplifications
  • ARG — unused arguments

Important:

  • Prefixes matter more than individual rule numbers
  • You usually enable/disable families, not single rules

Core Configuration File

Ruff is configured via pyproject.toml.

Minimal example:

[tool.ruff]
line-length = 88
target-version = "py311"

This already enables a large default rule set.

Rule Selection Strategy

Explicit Is Better Than Implicit

Ruff encourages explicit rule selection:

[tool.ruff]
select = ["E", "F", "I", "B"]

Meaning:

  • Enable only these rule families
  • Everything else is off unless added

This avoids “rule creep”.

Incremental Strictness

Common senior-level strategy:

select = ["E", "F"]
extend-select = ["I", "B"]

Interpretation:

  • Core correctness first
  • Gradually add stricter domains

Ignoring Rules (Globally vs Locally)

Global Ignore

ignore = ["E203", "E266"]

Use sparingly.

Global ignores:

  • Apply everywhere
  • Encode long-term policy decisions

Bad smell:

  • Long ignore lists without justification

File-Specific Ignores

[tool.ruff.per-file-ignores]
"__init__.py" = ["F401"]
"tests/**.py" = ["ARG", "S"]

This is where Ruff shines.

Use cases:

  • Re-export patterns
  • Test code flexibility
  • Legacy modules

Rule:

Prefer per-file ignores over global ignores.

Import Sorting (isort Replacement)

Ruff replaces isort with I rules.

Example:

[tool.ruff]
select = ["I"]

Key properties:

  • Deterministic ordering
  • No runtime import analysis
  • Fast enough to auto-fix everywhere

Ruff handles:

  • Stdlib vs third-party vs local
  • Section ordering
  • Alphabetization

Auto-Fix Behavior

Ruff distinguishes:

  • Safe fixes
  • Unsafe fixes

Default:

  • Safe fixes only

Enable fixing:

[tool.ruff]
fix = true

Or in CI:

ruff check --fix

Senior guideline:

  • Auto-fix style
  • Review logic-related fixes

Complexity Controls

Ruff includes McCabe-style complexity checks.

[tool.ruff]
select = ["C90"]
max-complexity = 10

Important:

  • Complexity limits are signals, not truths
  • Use them to identify refactoring targets

Do not:

  • Enforce extremely low limits
  • Block PRs without context

Python Version Awareness

target-version = "py311"

This enables:

  • Syntax checks
  • Upgrade suggestions (UP rules)
  • Removal of compatibility clutter

This is not type checking. It is syntax-level correctness.

Lint vs Fix Philosophy

Ruff can:

  • Report only
  • Fix only
  • Report + fix

Healthy setup:

  • Auto-fix on save (local)
  • Strict linting in CI
  • Minimal manual intervention

Anti-pattern:

  • Running Ruff only in CI

Ruff in CI (High-Level Pattern)

Typical usage:

ruff check .

Strict mode:

ruff check . --select ALL

Migration mode:

ruff check . --exit-zero

Senior takeaway:

  • Ruff is fast enough to run everywhere
  • CI should enforce, not surprise

Ruff vs Flake8 (Configuration Perspective)

AspectRuffFlake8
Config locationpyproject.tomlsetup.cfg / tox.ini
Rule selectionUnifiedPlugin-based
PerformanceExtremely fastSlow at scale
Fix supportNativeLimited
Cognitive loadLowHigh

Key insight:

  • Ruff shifts linting from “tool wrangling” to “policy definition”

Common Ruff Misuse Patterns

  • Enabling ALL rules immediately
  • Disabling rules without understanding them
  • Treating Ruff output as suggestions instead of policy
  • Mixing formatting concerns with architectural concerns
[tool.ruff]
line-length = 88
target-version = "py311"
select = ["E", "F", "I", "B", "UP"]
ignore = []
fix = true

Then:

  • Add per-file ignores
  • Add complexity checks selectively
  • Add stricter rule families intentionally

One-Sentence Mental Model

Ruff is not “a faster Flake8” —
it is a centralized rule engine that turns Python code quality into enforceable, low-friction policy.