Ruff: A Rust-Based Linter and Formatter That Replaces a Python Toolchain
Ruff is a Python linter and code formatter written in Rust, bundling over 900 rules with Flake8, isort, and Black compatibility plus automatic error fixing.
At a glance
- What is it?
- Ruff is a single Rust binary that lints, formats, and sorts imports for Python, claiming 10-100x speedups over Flake8 and Black. It targets teams tired of managing multiple tools, but its aggressive rule set and formatter parity demands a migration check.
- Who is it for?
- Adopt Ruff if you want a single, fast tool to replace Flake8, Black, isort, and several plugins, and if you can tolerate its formatter's near-Black but not identical output. Do not adopt it if you rely on a niche flake8 plugin that Ruff has not reimplemented, or if your team requires byte-for-byte Black formatting.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The Problem: Python Tooling Sprawl and Slow Feedback Loops
Python projects traditionally need separate tools for linting, formatting, import sorting, and modernizing syntax. Flake8 handles style and common bugs, Black reformats, isort sorts imports, and pyupgrade rewrites old syntax. Running all of them sequentially is slow, and each has its own configuration file and plugin ecosystem. Ruff solves this by combining those responsibilities into one Rust binary. The README claims it is 10-100x faster than existing linters and formatters, and it can replace Flake8 plus dozens of plugins, Black, isort, pydocstyle, pyupgrade, and autoflake. The target user is a Python developer or team that wants faster feedback in local development and CI, without juggling multiple tools. The speed claims come from the project's own benchmarks and testimonials, not from independent measurement, so treat them as directional.
How Ruff Works: A Single Binary with Built-in Caching
Ruff is written in Rust, which gives it a compiled, single-binary distribution. The README highlights built-in caching to avoid re-analyzing unchanged files, which is how it maintains speed across repeated runs. The linter has over 900 built-in rules, including native re-implementations of popular Flake8 plugins like flake8-bugbear. This means you do not install plugins; the rules are compiled into the binary. The formatter aims for drop-in parity with Black, and the import sorting matches isort's behavior. The architecture is not documented in the README, but the caching and rule count suggest a tree-sitter or custom parser that builds an AST once and runs all checks against it. This is a different approach from Flake8, which loads plugins as Python modules at runtime, adding overhead. Ruff's design trades plugin flexibility for speed and a unified interface.
Getting Started: Installation and Basic Commands
Ruff is available on PyPI, so you can install it with `pip install ruff`, or use `uv tool install ruff@latest` for a global install, or `uv add --dev ruff` for a project dependency. There are also standalone installers for macOS, Linux, and Windows, which avoid requiring a Python environment. The README shows example commands: `ruff check` lints all files in the current directory, and `ruff format` formats them. You can also invoke it directly with `uvx ruff@0.16.5 check` to run a specific version without installing. Configuration lives in `pyproject.toml`, which Ruff reads automatically. The README mentions hierarchical and cascading configuration, meaning you can have per-directory overrides, which is useful for monorepos. For a quick start, you run `ruff check` and `ruff format` on a codebase and see what changes. The tool also has a playground at play.ruff.rs to test rules without installing.
Rule Coverage and Fix Support: More Than a Linter
Ruff's linter includes over 900 rules, covering standard flake8 checks, bugbear-style bug detection, and even pyupgrade rules that modernize syntax. The README mentions automatic fix support, such as removing unused imports. This is a significant advantage over Flake8, which often requires separate fixers like autoflake. The rules are grouped by prefix, similar to flake8's plugin codes, but you configure them in `pyproject.toml` under `[tool.ruff.lint]`. The README does not list specific rule codes, so you would need to consult the docs for the full catalog. The breadth of rules means you can likely drop several tools, but it also means you need to audit which rules are enabled by default. Ruff's default rule set is not shown in the README, but it likely includes E and F codes from pycodestyle and pyflakes. For teams migrating, the risk is that a plugin you rely on is not reimplemented, forcing you to keep the old tool alongside.
The Formatter: Near-Black Parity, But Not Identical
Ruff's formatter claims drop-in parity with Black, but the README points to a FAQ that details differences. This is a genuine limitation: if your project has committed to Black's exact formatting, Ruff may produce different output in edge cases. The README says 'drop-in parity' but also acknowledges the comparison is not exact. For example, Black's handling of certain string concatenations or line breaks might differ. The formatter is designed to be fast, but speed is not the only trade-off. Teams that use Black with custom line length or skip-string-normalization will need to configure Ruff's `[tool.ruff.format]` section, which is not detailed in the README. The practical advice is to run `ruff format --check` in CI to see if your code is already compliant. If you have a large codebase, expect an initial reformatting diff, which can be disruptive. Ruff's formatter is not a drop-in replacement if you need byte-for-byte consistency with Black's output.
Configuration and Monorepo Support: Hierarchical and Cascading
Ruff's configuration system is one of its strengths. The README highlights hierarchical and cascading configuration, which means you can have a root `pyproject.toml` and per-directory overrides. This is useful for monorepos where different projects have different rule sets. For example, you might enable stricter rules for a core library but relax them for scripts. The configuration is done through `pyproject.tool.ruff` keys, but the README does not list specific keys. You would need the docs for that. The cascading behavior is similar to how ESLint works, where configs inherit and override. This is a contrast to Flake8, which uses a single `.flake8` file at the root or in the user's home directory. Ruff's approach gives you more control but also more complexity. If you have a simple project, the defaults might suffice, but for a monorepo, you need to plan your config hierarchy carefully to avoid unexpected rule changes.
Maintenance and License: Active Development, MIT, and Upgrade Costs
Ruff is under active development, with releases every week or two: 0.16.5 on 2026-08-27, 0.16.4 on 2026-08-20, and 0.16.3 on 2026-08-13. This cadence means new rules and fixes arrive frequently, but it also means you need to track updates for behavior changes. The project is backed by Astral, the company behind uv and ty, which suggests long-term support. The license is MIT, which is permissive for commercial use. The maintenance cost is low in terms of dependencies, since Ruff is a single binary, but you must update it regularly to get rule improvements. The README mentions Python 3.14 compatibility, so it keeps pace with new Python versions. For upgrade cost, you can pin a version in your `pyproject.toml` or use `uv` to manage it. The active development is a double-edged sword: you get fixes, but you also risk rule changes that alter your lint results. The alternative is to stay on a stable version and only upgrade when needed.
Alternatives and Trade-offs: Flake8, Black, and the Plugin Ecosystem
The main alternative is to keep using Flake8 with plugins, Black, and isort as separate tools. Flake8 offers a plugin ecosystem that Ruff cannot fully replicate, even with 900 rules. If you use a niche plugin like flake8-print or flake8-django, you need to check if Ruff has an equivalent. The README says Ruff reimplements popular plugins, but not all. The difference in approach is that Flake8 loads plugins dynamically, so you can add any plugin, while Ruff compiles rules into the binary, so you are limited to what the project has implemented. Another alternative is to use a tool like pyright or mypy for type checking, but that is a different concern. Ruff does not do type checking, so you still need a type checker. The trade-off is speed and integration versus flexibility. If your team uses a small set of plugins, Ruff is likely a drop-in replacement. If you rely on a long tail of plugins, you may need to keep Flake8 for those specific checks.
Editorial conclusion
Adopt Ruff if you want a single, fast tool to replace Flake8, Black, isort, and several plugins, and if you can tolerate its formatter's near-Black but not identical output. Do not adopt it if you rely on a niche flake8 plugin that Ruff has not reimplemented, or if your team requires byte-for-byte Black formatting. Before switching, run `ruff check` and `ruff format` on a representative subset, compare the diff against your current setup, and verify that your pre-commit hooks and CI can use the standalone installer or pip package. The project is under active development, so pin a version (like 0.16.5) to avoid surprises.
Community notes