Skip to main content

Ruff Runs 10 to 100 Times Faster Than Flake8 and Black

Every Python team eventually builds the same pile of tools. Flake8 for linting, a handful of plugins for import order and unused arguments, isort for imports, pyupgrade for old syntax, Black for formatting, and a pre-commit hook to keep the pile from drifting. Each tool parses your code, and each one pays the startup cost of a fresh Python interpreter. On a large repository the combined run is often measured in minutes, which is exactly the kind of delay that makes developers skip the check locally and let CI catch it later.

Ruff attacks that pile directly. It reimplements the lint rules and the formatter in Rust, ships as a single binary, and its author, Charlie Marsh, built it around one blunt benchmark claim: it is roughly 10 to 100 times faster than Flake8 and Black depending on the workload. That range sounds like marketing until you watch it run. Ruff starts in milliseconds, walks the filesystem in parallel, and reuses a single parse of each file across every rule, so a check that used to take a minute and a half finishes before you can switch windows.

The project appeared in August 2022 and began as a replacement for Flake8 specifically. The ambition grew quickly. Within a year Ruff covered the rule sets of Flake8 and its most common plugins, then absorbed isort, pyupgrade, and a growing list of bugbear and bandit style checks. By the time version 0.1.0 landed in October 2023, the project had a versioning policy, a preview mode for unstable rules, and a formatter that aimed to be a near drop-in replacement for Black. Marsh's company, Astral, raised a seed round in April 2023 and later became known for the uv package manager as well, but Ruff remains the tool that made the case for writing developer tooling in Rust.

The practical effect is that the entire quality gate collapses into one command. A typical configuration lives in pyproject.toml under a single tool.ruff table, with line length, target Python version, and a selected list of rule codes. There is no plugin plumbing, no separate isort profile to keep in sync with Black, and no debate about which plugin version breaks on which Python release. One binary provides both the check and the format subcommands, and both accept the same file selection flags, so pre-commit, CI, and your editor all invoke the same thing with the same behavior.

The speed changes behavior, not just wall clock time. When a full repository lint takes under a second, you can run it on every save instead of every commit. You can run it across the whole tree in CI without sharding or caching tricks, and you can afford to enable rules that you would previously have considered too expensive, such as checks that need whole project import resolution. Teams that used to lint only the files touched by a pull request often switch to whole repository runs, because the incremental machinery ends up costing more than the check itself.

Ruff also made a careful decision about automated fixes that is worth copying in your own tooling. It classifies every fix as safe or unsafe, applies only safe fixes by default, and requires an explicit flag for the rest. That distinction matters because an autofix that silently changes runtime behavior is worse than a failing check. The rule set is large, more than nine hundred rules today, but the defaults stay conservative: only a small core of error rules is enabled out of the box, and you opt into the opinionated families one at a time.

The caveats are real but narrow. Rule parity is not perfect, and a few Flake8 plugins have no direct equivalent, particularly ones that depend on third party type information or framework specific conventions. The formatter is designed to match Black closely, yet small differences remain, so a repository that switches formatters should expect one large reformatting commit rather than a clean diff. Ruff also moves fast, which means pinning a version in CI and reading the changelog before upgrading, exactly as you would with any tool that owns your formatting.

What makes the Ruff story interesting beyond Python is the pattern it established. A small team picked a mature and well understood category, rewrote it in a systems language, and used raw speed as the adoption strategy. Speed lowered the cost of running the tool everywhere, which increased the number of places teams were willing to run it, which in turn made the tool part of the default workflow instead of an optional step. That is a more durable advantage than a feature checklist, and it is why so many Python projects now ship a ruff.toml in the repository root.

We use cookies for ads and analytics.