TL;DR: The Ruff formatter is an extremely fast Python formatter, written in Rust. It’s over 30x faster than Black and 100x faster than YAPF, formatting large-scale Python projects in milliseconds — all while achieving >99.9% Black compatibility.


A little over a year ago, I made the first commit to Ruff, an extremely fast Python linter, written in Rust. Since then, Ruff has grown to millions of downloads per week with support for hundreds of lint rules, serving as a drop-in replacement for dozens of tools like Flake8, isort, and pyupgrade.

Today marks our biggest release since the linter itself: Ruff’s Python formatter, available now in Beta with pip install ruff and ruff format.

0s 5s 10s 15s 20s ruff black yapf autopep8 3.20s 17.77s 19.56s 0.10s

Formatting the Zulip codebase (~250,000 lines of code), without caching.

The formatter’s design and implementation followed the same guiding principles as the linter:

  1. An obsessive focus on performance. Ruff’s formatter is over 30x faster than Black and 100x faster than YAPF, formatting large Python projects in milliseconds. Even with caching disabled, Ruff’s formatter is faster than Black with caching. It's really fast!
  2. Optimized for adoption. Ruff’s formatter is designed as a drop-in replacement for Black. On Black-formatted Python projects, Ruff achieves >99.9% compatibility with Black, as measured by changed lines. When formatting the Django codebase, for example, Ruff and Black only differ on 34 out of 2,772 files. Focusing on compatibility enables projects to adopt the Ruff formatter with minimal disruption.
  3. Configurable, with familiar defaults. Ruff’s formatter aims to be drop-in compatible with Black out-of-the-box — which means great defaults with zero configuration. But it does expose a few additional settings. For example, users can configure the desired quote (single vs. double) and indentation styles.
  4. A simplified toolchain. ruff format is built into Ruff, rather than shipping as a separate package, allowing you to simplify your project’s dependencies and learn one fewer CLI. Our goal is to build a Cargo-like experience for Python: a streamlined workflow whereby your tools “just work” and compose into a unified toolchain.

Beyond the core features you’d expect, Ruff’s formatter also includes:

  • Support for parsing and formatting Python 3.12 code.
  • Built-in support for formatting Jupyter notebooks.
  • Editor integrations via our VS Code extension and language server, along with community-maintained extensions (like the Ruff IntelliJ plugin), for instant format-on-save on multi-thousand line files.

Give it a try today with pip install ruff and ruff format.

Why Build a Formatter?

In the Python community, Black is the overwhelmingly popular choice for code formatting. I’ve been a Black user and fan for a long time — it's a great tool! So, why build a formatter?

With the Ruff formatter, our goal is not to innovate on code style, but rather, to innovate on performance, and to provide a unified experience across Ruff's linter, formatter, and future tools.

We believe that a unified toolchain will result in a better set of tools, with less complexity. As we improve our parser and other core infrastructure, the linter and formatter will both benefit. As we deepen the integration between the linter and the formatter, both tools get stronger — for example, in the future, we'll automatically format the linter’s code fixes, and raise lint violations for invalid # fmt: on and # fmt: off comments.

With a unified toolchain as our North Star, we had two key goals for the formatter:

1. Extremely Fast

With the formatter, we aimed for order-of-magnitude performance improvements. When a tool is 10x, or 20x, or 100x faster, it completely changes the ergonomics. A setup that once required “only running on changed files” can now run over your entire codebase in less time.

This is of course true for large projects, where an order of magnitude means minutes to seconds, and seconds to milliseconds. But even for smaller projects, the difference in responsiveness — both on the command line and in the editor — is addictive.

Formatting a variety of large Python projects via `ruff format`

Formatting over 2,000,000 lines of Python, without caching, in milliseconds.

As with the linter, our bar for performance is: "crazy fast", "way too fast", "so fast I always think it didn't run", "so fast that I thought it failed", etc.

2. Black Compatible

From the start, our goal has been to deliver a drop-in replacement for Black — a formatter that projects can adopt with minimal churn and disruption.

When benchmarked on popular Black-formatted projects, like Django and Zulip, ruff format achieves >99.9% compatibility with Black, as measured by changed lines. When migrating an existing project to Ruff’s formatter, the vast majority of lines will be formatted identically, with a few known differences on the margins.

In general, we biased towards mimicking Black’s code style — unless we found a compelling reason to diverge. For example: unlike Black, the formatter excludes pragma comments when measuring line length, which prevents the addition of a # noqa comment from initiating a reflow, thus avoiding the frustrating loop of adding a # noqa comment only to have it move around during formatting.

Like Black, the formatter implements a single code style, but — unlike Black — it exposes a few additional configuration options. For example, the formatter allows users to choose their preferred quote and indentation style:

[tool.ruff.format]
quote-style = "single"
indent-style = "tab"

While Black has an excellent style guide, achieving this level of compatibility was very challenging given the differences in architecture between the two tools. We spent a lot of time reverse-engineering and invested in an extensive test suite — extending Black's own tests with additional coverage of even the most unrealistic edge-cases.

Production-Ready

While this is a Beta release, we consider the Ruff formatter production-ready. We’ve been using it in production for a while now, as have our alpha users from GitHub and Discord — including Dagster, whose monorepo contains over 50 Python packages and 500,000 lines of code.

Our goals for a Stable formatter release are focused on additional functionality and deeper integration with the linter. Specifically, the Stable release will include:

  • Support for Black’s preview style. (The Beta release is limited to supporting Black’s stable style.)
  • Support for range formatting in our language server and editor integrations.
  • Support for formatting within f-strings.

During the Beta, we are especially interested in feedback on (1) formatter configuration, and (2) deeper integrations between the formatter and the linter. We'd love to hear from you.

The Astral Toolchain

Beyond the formatter itself, this release represents a significant milestone for Ruff as a project and Astral as a company, for two reasons:

  1. It’s our first time shipping a tool beyond the linter. The formatter is an entirely new thing. Though it shares some infrastructure with the linter (e.g., our lexer and parser), shipping the formatter required us to update Ruff’s internals, APIs, and documentation to accommodate a multi-tool architecture — work that we’ll be leveraging as we start on our Next Thing.
  2. It’s our first time building something new as a team. The first draft of the Ruff linter was written by me, in a cave. (We’ve now grown to a core team of five, with over 290 contributors.) This time, development of the formatter was led entirely by people that very much aren’t me: Micha Reiser and konsti. On a personal note, watching them produce something this high-quality from thin air was tremendously rewarding — I’m lucky to call them my colleagues.

As our team grows, so too does the scope of work that we can take on. Over the next few months, we’ll continue to invest heavily in our linter and formatter while kicking off a couple new efforts in pursuit of a unified Python toolchain.

If you find these kinds of problems exciting: we're hiring.

Acknowledgements

Finally, we'd like to thank all those that contributed directly to the development of the Ruff formatter, including Anders Kaseorg, Calum Young, Chris Pryer, David Szotten, Dimitri Papadopoulos, Harutaka Kawamura, Jeong YunWon, Jonathan Plasse, Louis Dispa, Luc Khai Hai, Tom Kuson, Victor Hugo Gomes, cosmojg, magic-akari, and qdegraaf, along with those community members that provided early feedback.

We'd also like to thank a few individuals from the broader community: