Context
Ruff and Flake8 solve the same problem (surfacing lint errors and style deviations in Python code), but from opposite foundations. Flake8 is the established checker of the ecosystem: a pure-Python orchestrator that bundles three classic tools, pyflakes (logic errors), pycodestyle (PEP 8 style) and mccabe (complexity), and extends through a large ecosystem of plugins like flake8-bugbear. Ruff, built by Astral, is a single binary written in Rust that reimplements the rules of Flake8, isort, pyupgrade, pydocstyle and dozens of other tools, runs orders of magnitude faster, fixes much of what it finds on its own with --fix, and even ships a built-in formatter that is Black-compatible.
The decisive variable is not which one finds more issues: it is whether you want a single, fast tool with auto-fix replacing the whole stack, or whether you depend on specific plugins and a stable API that years of ecosystem have validated. Ruff is pre-1.0 (0.x versions): the rule set and some defaults still shift between releases, which is the price of how fast it moves.
When to choose Ruff
Ruff makes the most sense when lint slowness is a pain, or when the quality-tooling stack has become a zoo of configs. A project that today runs flake8 + isort + pyupgrade (and maybe black to format) can collapse all of it into one binary: ruff check lints with the equivalent rules, ruff check --fix automatically fixes what it safely can, ruff format formats in the Black style, and isort's import sorting becomes a rule (I) inside the same pass. On large codebases the speed difference is dramatic: what took tens of seconds now runs in fractions of a second, which changes the ergonomics in the editor and in pre-commit.
# pyproject.toml
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
# equivalents to pyflakes (F), pycodestyle (E), isort (I), bugbear (B), pyupgrade (UP)
select = ["E", "F", "I", "B", "UP"]
ruff check --fix . # report and fix what is safe
ruff format . # format in the Black style
At around 337 million downloads per week on PyPI, Ruff has become the default in new projects, driven precisely by that unification. The cost is maturity: being on 0.x, its rule catalog and some behaviors still evolve, and it does not accept third-party plugins, so a very specific rule that only exists as a Flake8 plugin may not have an equivalent yet. Pin the version in your lockfile so you are not surprised by changes between releases.
When to choose Flake8
Flake8 is still the right call when the project depends on a specific ecosystem plugin that Ruff has not reimplemented yet, or when stability matters more than speed. Years as the reference checker produced an ecosystem of production-tested plugins and a predictable API: configuration rarely changes behavior between versions, and each rule has a known, documented code. For a codebase that is already configured, mature, and whose lint is not a time bottleneck, migrating brings little immediate payoff.
# .flake8 (or setup.cfg)
[flake8]
max-line-length = 100
max-complexity = 10
extend-select = B # enables the flake8-bugbear plugin
extend-ignore = E203
flake8 . # reports the issues; fixing is manual
The difference shows up in the same flow: Flake8 reports but does not fix, and formatting or sorting imports is left to separate tools (black, isort), each with its own configuration. In exchange, you get a stable rule set and access to niche plugins that do not yet exist in Ruff. At around 72 million downloads per week, Flake8 remains widely installed, sustained by CIs and mature projects with no strong reason to swap a configuration that works.
Verdict
Reduced to its essence, the choice turns on a trade-off: speed and unification against stability and plugins. Ruff replaces the whole stack (linter, formatter, import sorter) with a single, fast binary that auto-fixes, and for the vast majority of projects, especially new ones, it is the decision that saves CI and configuration time from the start. The honest caveat is the 0.x stage: pin the version and expect rules and defaults to still move between releases.
Flake8 rarely disappoints when what you need is a stable API or a specific ecosystem plugin that Ruff does not cover yet. If the codebase is already configured, lint is not a bottleneck, and there is a real plugin dependency, there is no urgency to migrate. But for a project starting today, or for anyone suffering from slow lint and scattered configs, Ruff is the natural bet, and its growing adoption reflects that. Choose by the mix of speed and scope your project actually needs, not by habit.