Skip to main content
Guides

Ruff vs Pylint: Which Python Linter Should You Use in 2026?

By Mithun··10 min read
Quick Answer

Choose Ruff for the fastest everyday feedback, built-in formatting, and safe automatic fixes. Keep Pylint when you rely on deeper inference, cross-file checks, or plugins. On our same 14-category fixture, Ruff 0.16.3 caught 6 categories with its defaults and 9 with an expanded profile; Pylint 4.0.7 caught all 14. Ruff’s repeated fresh-process median was 23.3 ms versus Pylint’s 1125.5 ms—about 48× faster on this small test. For many teams, the practical setup is Ruff plus a type checker, with Pylint retained only for checks Ruff does not cover.

“Ruff replaces Pylint” sounds tidy, but it hides two different questions. Can Ruff replace Pylint’s fast, everyday lint feedback? Often, yes. Does Ruff perform every semantic, cross-file, and plugin-based check Pylint can? No—and Ruff’s own documentation says it is not a pure drop-in replacement.

For this comparison, we installed Ruff 0.16.3 and Pylint 4.0.7 together under CPython 3.14.2, then ran both over the same retained eight-file package. The fixture contains 14 deliberate categories: ordinary lint mistakes, risky patterns, semantic call errors, duplicate code, and an import cycle. We also tested Ruff’s automatic fixer and timed 11 fresh processes per configuration. Here is what actually happened.

Ruff vs Pylint: the tested answer

QuestionObserved answer
Which caught more categories?Pylint: 14/14; expanded Ruff: 9/14; default Ruff: 6/14
Which was faster here?Ruff: 23.3 ms repeated median vs Pylint 1125.5 ms
Which fixed code?Ruff: removed both unused imports with --fix
Which found semantic call errors?Pylint: missing member and missing argument
Which found cross-file issues?Pylint: duplicate code and an import cycle
Which also formats Python?Ruff: ruff format; Pylint is an analyzer

That does not make Pylint the universal winner. Ruff completed this small package roughly 48× faster, produced tighter output, and fixed safe violations. Pylint found more kinds of problems, but its default run also added convention and design messages that a team may not want. The choice is feedback speed and consolidation versus deeper analysis and extensibility.

Raw diagnostic totals are not a scoreboard. One category can create several messages, and Pylint’s defaults include conventions and refactoring advice that Ruff does not enable by default. The category table below is the useful comparison.

How the test was designed

The controlled package has eight Python files and 159 physical lines. Every intentional issue is tagged in the retained fixture. One clean module checks that the selected bug profiles do not invent a problem in valid narrowing code.

import json # unused import
def undefined_name() -> object:
return missing_value
def append_item(items: list[str] = []) -> list[str]:
items.append("value")
return items
def parse_number(value: str) -> int | None:
try:
return int(value)
except Exception:
return None
class Account:
def __init__(self) -> None:
self.balance = 10
owner = Account().owner # semantic member error
A shortened sample; both tools received the full same package.

We ran Ruff twice: its current defaults, then an explicit expanded profile (E,F,W,B,BLE,I,PL,RET,SIM,UP,S307). Pylint also ran twice: defaults, then only the 14 relevant symbols. These profiles make defaults and targeted coverage visible separately; they are not claimed to be perfectly matched rule sets.

ruff check --no-cache sample_project
ruff check --no-cache --select E,F,W,B,BLE,I,PL,RET,SIM,UP,S307 sample_project
pylint --persistent=no --jobs=1 --score=no sample_project
pylint --persistent=no --jobs=1 --score=no --disable=all --enable=<14 relevant symbols> sample_project
Core commands used in the retained test.

Which errors did Ruff and Pylint catch?

Error categoryRuff defaultRuff expandedPylint
Unused importCaughtCaughtCaught
Undefined variableCaughtCaughtCaught
Mutable default argumentCaughtCaughtCaught
Broad Exception catchCaughtCaughtCaught
Use of evalCaughtCaught
Too many argumentsCaughtCaught
Boolean compared with TrueCaughtCaught
Unused local variableCaughtCaughtCaught
Text file opened without encodingCaught
subprocess.run without checkCaughtCaughtCaught
Missing instance memberCaught
Missing function-call argumentCaught
Duplicate code across filesCaught
Import cycleCaught
Total categories6/149/1414/14

Ruff’s 8 default diagnostics represented 6 categories because three unused imports each produced F401. Its expanded run produced 12 diagnostics across 9 categories; the too-many-arguments case produced both PLR0913 and PLR0917. Pylint’s focused run produced 16 diagnostics across all 14 categories.

The official Ruff FAQ explains the boundary: Pylint performs more type inference, the tools have overlapping but different rules, and Ruff is not a pure Pylint replacement. Our missing-member and missing-argument results are concrete examples of that difference.

Defaults matter more than rule-count headlines

ConfigurationDiagnosticsTarget categories
Ruff default86/14
Ruff expanded129/14
Pylint default2314/14 plus convention/design messages
Pylint relevant rules only1614/14

Pylint’s 23-message default output included six convention messages, three errors, five refactor messages, and nine warnings. Besides the target cases, it commented on trailing blank lines, a class with too few public methods, and both total and positional argument thresholds. That can be valuable—or noisy—depending on the team.

Ruff’s defaults were more practical than old descriptions of “only E and F rules” imply: Ruff 0.16.3 also caught the mutable default (B006), broad exception (BLE001), and unchecked subprocess call (PLW1510) without our expanded selector. Still, enabling a Ruff prefix such as PL does not recreate all Pylint analysis.

When comparing on your repository, save each tool’s effective configuration beside the output. “Ruff found fewer errors” may simply mean fewer rules were enabled; “Pylint found more” may include conventions your team intentionally ignores.

Speed: Ruff was about 48× faster on this small package

Each configuration started as a new process 11 times on Windows with an AMD Ryzen 5 3500. Caches were disabled, Pylint used one job, and output was discarded. The first timed process is separate; the repeated number is the median of the next ten.

ConfigurationFirst timed processRepeated medianRepeated range
Ruff default40.9 ms23.3 ms22.6–24.9 ms
Pylint default1165.4 ms1125.5 ms1116.4–1149.8 ms
Ruff expanded40.7 ms23.4 ms21.7–25.5 ms
Pylint relevant rules1157.0 ms1132.9 ms1106.1–1320.7 ms

The repeated default medians differ by about 48.3×; the expanded/focused medians differ by about 48.4×. That supports Ruff’s fast-feedback advantage on this machine, but not a universal ratio. This package is tiny, process startup dominates, and no editor daemon, cache, parallel Pylint run, or large repository was measured.

If you want to feel the feedback loop rather than read milliseconds, paste a mistake into the live Ruff Playground. Diagnostics update as you edit, and you can change the rule configuration without installing anything.

Automatic fixes and formatting: Ruff’s clearest advantage

A separate fixture contained two unused imports. ruff check --fix reported 2 errors, 2 fixed, 0 remaining and removed both imports. Pylint 4.0.7 reported unused imports but exposed no general --fix command in its CLI help.

# before
import os
import sys
def greeting(name: str) -> str:
return f"Hello {name}"
# after ruff check --fix
def greeting(name: str) -> str:
return f"Hello {name}"
The fix test; Ruff removed both import lines.

Lint fixes and formatting are separate. ruff check --fix repairs supported violations; ruff format rewrites layout. The Ruff Linter & Formatter Online exposes both jobs in a simpler one-click interface, while the Ruff Playground adds editable configuration, AST, tokens, and formatter IR.

Ruff documents fixes as safe or unsafe. The official linter guide recommends reviewing unsafe fixes because they can change runtime behavior or remove comments. Fast automation is useful precisely when the boundary stays visible.

Where Pylint still found problems Ruff missed

  • Missing member: Account().owner triggered Pylint E1101; neither Ruff profile reported it.
  • Missing call argument: calling a two-parameter function with one argument triggered Pylint E1120; Ruff did not report it.
  • Duplicate code: Pylint compared two files and flagged a 15-line repeated implementation with R0801.
  • Import cycle: Pylint followed two package imports and reported R0401; Ruff did not.
  • Missing encoding: Pylint flagged text open() without an explicit encoding; the tested Ruff profile did not.

These are exactly the cases a one-file syntax-oriented pass can miss. Pylint builds on astroid for inference and project analysis. It also supports third-party checkers, while Ruff currently implements rules natively and does not support third-party plugins, as documented in the official Ruff comparison.

Pylint is still not a substitute for a real type checker. Ruff’s own guidance recommends pairing linting with mypy, Pyright, or another type checker. Our tested ty vs mypy vs Pyright comparison covers that separate decision.

Can Ruff replace Pylint?

Ruff can replace Pylint for some teams, but not by matching the package name alone. It works when the team mainly wants rapid linting, import cleanup, modernization rules, formatting, and a type checker already covers deeper call/type errors. It is not a complete replacement when the project depends on Pylint-only inference, cross-file checks, custom checkers, or framework plugins.

Your situationRecommended starting point
New project, fast feedback and one configRuff + a type checker
Existing Pylint project with custom pluginsKeep Pylint; add Ruff gradually
Need formatting and automatic lint fixesRuff
Need duplicate-code and import-cycle checksPylint, or another dedicated analyzer
Need quick browser testingRuff Playground or Ruff Linter & Formatter
Unsure which Pylint checks matterRun both temporarily and classify disagreements

A safe migration is additive: run Ruff without removing Pylint, enable the Ruff groups you actually want, let Ruff fix safe issues, then list every remaining Pylint-only diagnostic. Keep Pylint for that list or replace each item deliberately. Do not delete the old gate because a generic benchmark was fast.

A practical 2026 Python quality stack

  1. Ruff on every save and commit for quick linting, imports, modernization, and formatting.
  2. mypy, Pyright, or ty in CI for annotation-based type errors Ruff is not designed to prove.
  3. Pylint only where it adds value: semantic/cross-file checks, a required plugin, or project-specific policy.
  4. Tests for runtime behavior no static tool can guarantee.

Try the same code in both site tools: use the Ruff Linter & Formatter when you want a fast lint-or-format result, and open the Ruff Playground when you want live diagnostics, raw settings, a shareable case, or parser views.

The browser tools currently use Ruff 0.16.2; this CLI comparison used Ruff 0.16.3. That one-patch difference is disclosed because rules can change at the margins. The central semantic and cross-file limitations tested here are not inferred from the browser tool.

Reproduce the test and interpret it honestly

The retained evidence includes the keyword brief, all eight source files, the pre/post fix fixture, exact commands, diagnostic transcriptions, four sets of 11 timing samples, machine details, and limitations. The clean conclusion is narrower than “one tool wins”: Ruff optimized the feedback loop; Pylint inspected more relationships.

Repeat the comparison on your repository before changing CI. Include framework modules, generated code, plugins, type-heavy APIs, and the editor workflow developers actually use. Pylint’s running guide and Ruff’s configuration guide are the primary references for making the two runs reproducible.

Test the difference on your own code

Paste a Python example into the live Ruff Playground, change the enabled rules, inspect every diagnostic, then compare the result with the Pylint checks your project still needs.

Open the Ruff Playground

Free tools mentioned here

Related guides

Frequently asked questions

Is Ruff better than Pylint?

Ruff is better for speed, automatic fixes, formatting, and consolidating several everyday tools. Pylint found more categories in our controlled test: 14/14 versus 6/14 for Ruff defaults and 9/14 for expanded Ruff. Pylint was stronger on semantic and cross-file cases, so “better” depends on which checks your project needs.

Can Ruff completely replace Pylint?

Not for every project. Ruff can replace Pylint when a team mainly needs fast linting and already uses a type checker, but it does not reproduce every Pylint inference rule, cross-file check, or third-party plugin. In our test, Ruff missed a missing member, missing call argument, duplicate code, and an import cycle that Pylint caught.

How much faster is Ruff than Pylint?

On our eight-file, 159-line Windows fixture with caches disabled, Ruff default had a 23.3 ms repeated fresh-process median and Pylint default had 1125.5 ms—a difference of about 48.3×. This startup-heavy small test is not a universal large-project or editor benchmark.

Does Ruff automatically fix Pylint errors?

Ruff can automatically fix supported Ruff violations, including many rules derived from other linters, but it does not fix every Pylint diagnostic. In our separate test, ruff check --fix removed both unused imports. Safe and unsafe Ruff fixes are distinguished, and formatting still requires ruff format.

Should I run Ruff and Pylint together?

Run both temporarily when evaluating a migration. Keep Ruff for fast linting and formatting, then identify the Pylint-only checks your codebase actually values. You may keep Pylint for those checks, or use Ruff plus a type checker if that combination covers the project without losing important diagnostics.

Can I try Ruff online without installing it?

Yes. Pyobfuscate has a quick Ruff Linter & Formatter for one-click checking and formatting, plus a full Ruff Playground with live diagnostics, editable settings, AST, tokens, formatter IR, and shareable links. Both run Ruff in the browser.

Keep reading