Ruff vs Flake8 and Black: I Replaced Both and Benchmarked It
Ruff is a single Rust tool that replaces Flake8, isort, pyupgrade *and* Black. In my test it linted 1,677 Python files in 0.28 s versus Flake8's 14.72 s — about 53× faster — for the same rule set, and its ruff format produces Black-compatible output. It also catches things Flake8 can't on its own, like unsorted imports (isort's job). The one honest caveat: Ruff's *default* rule set is curated and smaller than Flake8's, so enable more with --select if you want stricter checks. You can try it right now, no install, in our Ruff linter & formatter online.
For a decade a "proper" Python lint setup meant gluing together Flake8 (plus a pile of plugins), isort for imports, pyupgrade for modern syntax, and Black for formatting — four tools, four configs, four things to keep in sync. Ruff, from Astral, replaces all four with one binary written in Rust. I'd seen the "10–100× faster" headline everywhere and wanted to measure it myself, so I installed Ruff 0.16.1, Flake8 7.3.0 and Black 26.5.1 and put them head to head. Every number here is from my machine.
Below: a real speed benchmark on 1,677 files, real lint output on a deliberately messy file, the formatter compared to Black, and an honest look at where Ruff's defaults differ from Flake8's. If you just want to try it, our Ruff linter online runs the official Ruff WebAssembly build in your browser — paste code, get results, nothing uploaded.
What Ruff actually replaces
Ruff isn't just "a faster Flake8." It re-implements the checks of an entire toolchain in one place, so a single ruff check (and ruff format) covers what used to need four separate tools:
| Old tool | Job | Ruff covers it? |
|---|---|---|
| Flake8 | style + error linting (F, E codes) | Yes — ruff check |
| isort | sort/group imports | Yes — rule I001 |
| pyupgrade | modernize syntax | Yes — UP rules |
| Black | code formatting | Yes — ruff format |
That consolidation is the real story. One tool, one config block in pyproject.toml, one thing to install. And because it's Rust, it's fast enough to run on every keystroke in your editor and on every file in CI without anyone noticing the wait.
The speed benchmark (real numbers)
I pointed both linters at the same real codebase — 1,677 Python files from a populated virtualenv (FastAPI, huggingface-hub, numpy and their dependencies) — restricted both to the same F rule set for a fair fight, and disabled Ruff's cache so it did the full work every run:
| Linter | Time (1,677 files, F rules) | Relative |
|---|---|---|
| Ruff 0.16.1 | 0.28 s | 1× |
| Flake8 7.3.0 | 14.72 s | ~53× slower |
=== ruff check, 1677 files (best of 3, no cache) ===
0.44s
0.28s
0.28s
=== flake8, same files, same rules ===
14.72sFifty-three times faster is not a rounding error — it's the difference between a linter you run constantly and one you avoid. Flake8 is a Python program that imports pycodestyle and pyflakes and walks each file in Python; Ruff parses and checks in parallel across cores in native code. On a big repo that's the gap between a sub-second pre-commit hook and a coffee break.
The "10–100×" range you see quoted is real but depends on the rule set, file count and cache. With Ruff's cache warm (the normal case in an editor), re-checks are near-instant because it only re-lints changed files. My 53× is a cold, cache-disabled run — a deliberately conservative comparison.
Real lint output: Ruff catches what Flake8 misses
Speed is nice, but coverage matters more. I ran both on a small, deliberately messy file. Flake8 reported the unused imports and whitespace issues — but Ruff *also* flagged that the import block was unsorted (I001, isort's job) and offered to fix it, which Flake8 simply can't do on its own:
I001 [*] Import block is un-sorted or un-formatted
--> messy.py:1:1
help: Organize imports
F401 [*] `os` imported but unused
--> messy.py:1:8
help: Remove unused import: `os`
F841 [*] Local variable `unused_var` is assigned to but never usedThe [*] marker means Ruff can fix it automatically. Run ruff check --fix and it applies them — in my test it fixed 3 of 4 findings (the unused imports and the import sort) in one pass, leaving only the ones that need human judgement:
$ ruff check --fix --select F,I messy.py
Found 4 errors (3 fixed, 1 remaining).
No fixes available (1 hidden fix can be enabled with `--unsafe-fixes`).The formatter: ruff format vs Black
This is the "Ruff formatter" half people search for. ruff format is a near-drop-in replacement for Black — same opinionated style, same 88-column default, but far faster and built into the same tool. On my messy file it normalized quotes, fixed the mangled spacing, and added the missing whitespace around operators, exactly as Black would:
- result[x['id']] = x
+ result[x["id"]] = x
- def process( self ,data ):
- return [d for d in data if d!=None]
+ def process(self, data):
+ return [d for d in data if d != None]Single quotes became double quotes, process( self ,data ) became process(self, data), and d!=None became d != None — Black-compatible output from the same binary that does the linting. If you're already on Black, ruff format produces essentially identical results; if you're starting fresh, you get formatting and linting from one tool with one config.
You can watch both happen live without installing anything: paste a messy snippet into our Ruff linter & formatter online, switch between Lint and Format mode, and see the exact diagnostics and reformatting above run in your browser via WebAssembly — your code never leaves your machine.
The honest caveat: default rules differ
Here's the nuance most "Ruff is better" posts skip. On my messy file, Flake8 reported 10 issues while Ruff's defaults reported 5 — which looks like Ruff missing things. It isn't a bug; it's a philosophy difference:
- Flake8 enables the full pycodestyle style checker by default, so it flags every
E2xxwhitespace nit out of the box. - Ruff ships a smaller, curated default set (mostly
Fpyflakes rules and a few essentials) on the theory that you should *opt into* stylistic strictness rather than fight it. - The fix is one line. Add a
selectinpyproject.toml— e.g.[tool.ruff.lint] select = ["E", "F", "I", "UP", "B"]— and Ruff checks as much as Flake8 plus its plugins, and then some (it implements 900+ rules).
So don't judge Ruff by its default output count. Out of the box it's intentionally quiet; its power is that one select line unlocks the equivalent of Flake8 *plus* a dozen plugins (bugbear, comprehensions, pyupgrade, isort…) — all in the same fast binary. Configure it once and you'll never assemble a plugin list again.
Should you switch from Flake8 + Black?
For almost every project, yes. You collapse four tools into one, get a 30–50× speedup, and keep Black-compatible formatting. Migration is low-risk: Ruff reads a pyproject.toml config, understands most Flake8 rule codes, and ruff format matches Black closely enough that the reformat diff on an existing Black codebase is usually tiny. The realistic path is: add Ruff, set your select, run ruff format once, and delete Flake8, isort, pyupgrade and Black from your dev dependencies.
The only reasons to wait: a heavy investment in a niche Flake8 plugin Ruff hasn't reimplemented yet (rare now — it covers the popular ones), or a formatting style that deliberately diverges from Black. Otherwise, Ruff is the 2026 default. Try it on your own code first in the Ruff linter online — no install, runs in the browser — and if you like what you see, pip install ruff (or uv add ruff) and wire it into your editor and CI.
Try Ruff online — lint & format in your browser
Run the official Ruff WebAssembly build free in our Ruff linter & formatter online. Paste code, switch between Lint and Format, get instant results with rule codes and fixes. Nothing uploaded.
Open the Ruff Linter & FormatterFree tools mentioned here
Frequently asked questions
Is Ruff faster than Flake8?
Dramatically. In my benchmark linting 1,677 Python files with the same F rule set, Ruff took 0.28s versus Flake8's 14.72s — about 53× faster — with Ruff's cache disabled. Flake8 is a Python program that checks files serially; Ruff is a Rust binary that parses and lints in parallel across cores. With Ruff's cache warm (the normal editor case) re-checks are near-instant because it only re-lints changed files.
Does Ruff replace Black?
Yes. `ruff format` is a near-drop-in replacement for Black — same opinionated style and 88-column default — built into the same binary that lints. On an existing Black codebase the reformat diff is usually tiny to none, so you can delete Black and use ruff format. In my test it normalized single quotes to double, fixed spacing, and added whitespace around operators exactly as Black would.
What does Ruff replace?
Ruff consolidates four tools into one Rust binary: Flake8 (and its plugin ecosystem) for linting, isort for import sorting, pyupgrade for modernizing syntax, and Black for formatting. `ruff check` handles linting and import sorting; `ruff format` handles formatting. That means one install and one config block in pyproject.toml instead of four separate tools to keep in sync.
Why does Ruff report fewer errors than Flake8 by default?
Because Ruff ships a curated default rule set (mostly pyflakes F rules) while Flake8 enables the full pycodestyle style checker by default, so Flake8 flags more whitespace nits out of the box. It's a philosophy difference, not a gap in capability — add a select list in pyproject.toml (e.g. select = ['E','F','I','UP','B']) and Ruff checks as much as Flake8 plus its plugins, with 900+ rules available.
Can I use Ruff online without installing it?
Yes. Our Ruff linter and formatter online runs the official Ruff WebAssembly build directly in your browser — paste your Python, switch between Lint and Format mode, and get instant results with rule codes and auto-fix hints. Nothing is uploaded; all analysis happens locally on your machine. It's the fastest way to try Ruff before adding it to a project.
How do I migrate from Flake8 and Black to Ruff?
Install Ruff (pip install ruff or uv add ruff), add a [tool.ruff.lint] select list to pyproject.toml with the rule families you want (E, F, I, UP, B is a good start), run ruff format once to apply formatting, then remove Flake8, isort, pyupgrade and Black from your dev dependencies and update your pre-commit and CI to call ruff check and ruff format. Ruff understands most Flake8 rule codes, so your existing ignores usually carry over.