šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Ruff: The Fast Python Linter and Formatter

Ruff's rise to become the default Python linter — a single Rust-based tool replacing flake8, isort, pyupgrade, and a dozen other plugins, at a speed that changes how linting fits into a workflow.

⚔ Total XP: 0|šŸ’» python XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

What does select = ["E", "F", "I", "UP", "B"] in [tool.ruff.lint] actually configure?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Before Ruff, a well-linted Python project typically ran flake8, isort, pyupgrade, and several other single-purpose tools, each with its own configuration and its own noticeable runtime. Ruff, written in Rust, reimplements the rules from dozens of these tools in one binary, fast enough to run on every save.

1The Fragmented World Ruff Replaced

Before Ruff's rise, a thoroughly-linted Python project typically chained several genuinely separate tools together: flake8 (itself a wrapper combining pycodestyle for style and pyflakes for logical errors) for core linting, isort specifically for import ordering, pyupgrade for automatically modernizing syntax to take advantage of newer Python version features, and often several additional flake8 plugins (flake8-bugbear for common bug patterns, pep8-naming for naming conventions) each adding their own specific checks. Each of these was a separate package to install, a separate configuration section (or separate config file entirely) to maintain, and a separate step in both local development and CI — genuine coordination overhead, and genuine, if individually modest, runtime cost per tool that accumulated noticeably across a real, larger codebase.

Ruff, written in Rust specifically for performance, set out to reimplement the *rules* from this entire fragmented ecosystem — not just clone flake8's behavior, but genuinely re-derive and re-implement the specific checks that made each of these individual tools valuable — inside one single, fast, unified binary. select = ["E", "F", "I", "UP", "B"] in one [tool.ruff.lint] configuration section enables pycodestyle/pyflakes-equivalent checks (E/F), isort-equivalent import sorting (I), pyupgrade-equivalent modernization suggestions (UP), and flake8-bugbear-equivalent bug-pattern detection (B) — functionality that previously required five separate tool installations and configurations, now expressed as one line in one config section.

This consolidation mirrors exactly the same 'one file, one tool, replacing several fragmented ones' story pyproject.toml itself told relative to the old setup.py/setup.cfg/requirements.txt/pytest.ini era — Ruff applied that same consolidating philosophy specifically to the linting and formatting layer of the Python tooling ecosystem.

āœ•
—
+
# The old multi-tool setup:
# flake8    -- style and error checking
# isort     -- import sorting
# pyupgrade -- modernizing syntax for newer Python versions
# pep8-naming, flake8-bugbear, ... -- additional plugins
# Each: separate config, separate install, separate CI step
localhost:3000
Consolidated Tooling
select = ["E", "F", "I", "UP", "B"]
Five previously-separate tools' rules, one config, one binary

2Speed as a Genuine Feature, Not Just a Benchmark Number

Ruff's Rust implementation produces a speed difference from its Python-implemented predecessors that isn't merely a nice-to-have benchmark statistic — it's large enough (often reported as one to two orders of magnitude faster on real, sizable codebases) to genuinely change *how* linting fits into a development workflow. A linting step slow enough to take several seconds or longer creates real friction: developers start batching lint checks rather than running them constantly, or configure it to run only in CI rather than locally, or — in the worst case — start ignoring or ostensibly disabling it because the interruption to their flow outweighs the immediate value of the feedback.

A linting step fast enough to complete in a small fraction of a second removes that friction entirely, making 'run on every file save' (a common editor integration setting) genuinely viable rather than merely theoretically nice — the feedback loop between writing code and seeing lint/formatting issues shrinks from 'whenever I remember to run it, or whenever CI eventually runs it' to 'essentially immediately, every time I save.' This is precisely the same psychological and workflow effect the uv lesson identified for dependency resolution speed — an operation fast enough to feel instantaneous gets used far more readily and far more often than one requiring a deliberate pause.

This speed advantage is also why Ruff was adopted so rapidly and broadly across the Python ecosystem in a relatively short period — the value proposition (same or better rule coverage, one consolidated tool, dramatically faster) was compelling enough that migration effort was, for most projects, clearly worth it relative to the ongoing friction of the older, fragmented, slower toolchain.

āœ•
—
+
[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]  # pycodestyle, pyflakes, isort, pyupgrade, bugbear -- all in ONE tool

$ ruff check .   # runs ALL selected rule categories at once
localhost:3000
Speed That Changes Behavior
Fast enough for format/fix-on-save
Removes the friction that made linting easy to skip or defer

3ruff format and --fix: Beyond Just Reporting Problems

ruff format provides Black-compatible code formatting (Black, an earlier dedicated formatting tool, established many of the formatting conventions Ruff's formatter deliberately follows for compatibility) — automatically reformatting code to a consistent style, eliminating an entire category of code-review discussion about spacing, line breaks, and quote style that a deterministic formatter settles automatically rather than through human debate.

ruff check --fix goes beyond formatting into automatically *correcting* many lint findings directly, not merely reporting them — an unused import, an outdated syntax pattern pyupgrade's rules would flag, several other well-defined, unambiguous fixes are applied directly to your source files when you pass --fix, turning what would otherwise be a manual 'go fix each of these reported issues yourself' step into an automated correction, with the remaining, more genuinely judgment-requiring findings still reported normally for manual review.

This combination — format automatically, auto-fix what's safely fixable, and clearly report the rest — is what makes Ruff genuinely practical as a pre-commit hook or an editor's save-triggered action (the Pre-commit lesson later in this section covers exactly this integration pattern): most routine style and simple correctness issues are resolved automatically and silently, and only genuinely judgment-requiring findings interrupt the developer's actual attention, keeping the tool's value high relative to its interruption cost.

āœ•
—
+
$ ruff format .        # formats code (Black-compatible style)
$ ruff check --fix .   # automatically fixes many lint findings, not just lists them

# Both run in a fraction of a second on most real projects --
# fast enough for an editor's 'format/fix on save' setting
localhost:3000
Automated Correction
ruff format + ruff check --fix
Most issues resolved automatically — only genuine judgment calls surface

4Step-by-Step Breakdown

A linting step slow enough that developers start skipping it defeats its own purpose. Ruff's speed is fast enough that skipping it stops being tempting at all.

Before Ruff, a typical setup chained SEVERAL separate tools -- each with its own config file, its own installation, its own runtime cost.

ruff check reimplements the RULES from dozens of these tools in one Rust binary -- one install, one config section, one command.

Checkpoint: What does select = ["E", "F", "I", "UP", "B"] in [tool.ruff.lint] actually configure?

  • →Which categories of rules to enable (pycodestyle, pyflakes, isort, pyupgrade, bugbear) -- functionality that previously required several separate tools
  • →A setting specific to just ONE underlying check, similar to a single flake8 flag

ruff format replaces Black -- and ruff check --fix AUTOMATICALLY fixes many findings, not just reports them.

Checkpoint: What is the key difference between ruff check and ruff check --fix?

  • →--fix automatically applies fixes for many findings; plain check only reports them without modifying code
  • →They check entirely different sets of rules

Ruff handles linting and formatting speed; MyPy is the next tool, for the deeper, type-level correctness checking linting alone can't provide.

Check Real Import Sort Order. Finish is_sorted_imports(): Ruff's import-sorting rule checks exactly this.

Level Up šŸš€

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported (via server-side Python execution).

FirefoxSupported

Fully supported (via server-side Python execution).

SafariSupported

Fully supported (via server-side Python execution).

EdgeSupported

Fully supported (via server-side Python execution).

Best Practices

Configure Ruff to run on every file save in your editor, given its speed makes this genuinely practical

Ruff's speed removes the traditional friction of frequent linting — configuring it for immediate, on-save feedback is a realistic, valuable workflow improvement that slower tools couldn't practically support.

Use ruff check --fix and ruff format to auto-resolve routine findings, reserving manual review for genuine judgment calls

This keeps the tool's interruption cost proportional to its value — most style and simple correctness issues resolve automatically, while only findings genuinely requiring human judgment surface for review.

Frequent Bugs

THE BUG

Continuing to maintain a fragmented, older multi-tool linting setup (separate flake8, isort, pyupgrade configurations) out of inertia, missing Ruff's consolidation and speed benefits for an existing, already-configured project.

THE FIX

Migrate to Ruff's unified [tool.ruff] configuration, consolidating equivalent rule categories (select = [...]) from the previously separate tools into one config section and one CI step.

Real-World Examples

Migrating a Project From flake8 + isort + pyupgrade to Ruff

A team wants to consolidate their existing multi-tool linting setup into Ruff, reducing both configuration complexity and CI runtime.

# Before: separate configs for flake8 (setup.cfg), isort (.isort.cfg), pyupgrade (CLI flags in CI)

# After: one consolidated pyproject.toml section
[tool.ruff]
line-length = 100
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "C4"]
ignore = ["E501"]  # line length handled by the formatter instead

# CI step: just `ruff check .` and `ruff format --check .`

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Maintaining separate, redundant flake8/isort/pyupgrade configurations alongside a newly-added Ruff configuration, running both toolchains and duplicating effort and CI time.

# Redundant: both toolchains configured and running # setup.cfg: [flake8] ... # .isort.cfg: ... # pyproject.toml: [tool.ruff.lint] select = [...] # Correct: fully migrated, old configs removed # pyproject.toml only: [tool.ruff.lint] select = ["E", "F", "I", "UP", "B"]

The Solution //

Fully migrate rule selection to Ruff's [tool.ruff.lint] configuration and remove the older, now-redundant tool configurations and CI steps entirely, rather than running both in parallel indefinitely.

Lesson Glossary

[01]Ruff

A fast, Rust-based Python linter and formatter consolidating rules from flake8, isort, pyupgrade, and other tools into one binary.

Code Preview
// Ruff context

[02]Rule selection ([tool.ruff.lint] select)

Configuration specifying which categories of lint rules (mapped from historical tools) Ruff should enable.

Code Preview
// Rule selection ([tool.ruff.lint] select) context

[03]ruff format

Ruff's Black-compatible code formatter, providing deterministic, automatic code style formatting.

Code Preview
// ruff format context

[04]ruff check --fix

A Ruff command automatically applying fixes for well-defined lint findings directly to source files.

Code Preview
// ruff check --fix context

Continue Learning