Back to all articles

Comparing Ruff, Flake8, and Pylint | Linting Speed

By The Trunk TeamAugust 31, 2024
linter

Unlike other languages, the linter you pick when writing Python makes a substantial difference in developer experience. Missing tabs in Python aren’t just an eyesore. Python is a dynamically typed language that uses tabs to enclose scope. Missing tabs can break your program and cause hard-to-catch bugs.

Choosing the right linter can greatly impact your Python development process. Linters help catch errors and enforce code standards before you even run your program. Ruff, Flake8, and Pylint are popular choices, each with unique strengths. 

Introduction to Ruff, Flake8, and Pylint

What is Pylint?

Pylint has been the de facto standard for Python linting since 2021. It’s a linter offering comprehensive code analysis for finding errors, enforcing coding standards, and suggesting code improvements. This is the linter you’re most likely to see on older Python projects.

What is Flake8?

Flake8 is a similar linting tool to Pylint but with a few differences. Flake8 combines Pyflakes, pycodestyle(PEP8), and McCabe into a single tool. Let’s break down what this means. Pyflake in Flake8 is responsible for preventing errors. It runs faster than Pylint but only checks the syntax tree of each file individually. It scales better than Pylint, produces fewer false positives, but does less. Pycodestyle is a linter that adheres to the PEP8 standard, which means it enforces a subset of rules found in Pylint. The inclusion of the McCabe package is perhaps the most interesting. McCabe is used to check for “cyclomatic complexity,” which can be used to prevent a single function from being overly complex. Overly complex functions are harder to read, harder to maintain, harder to test, and ultimately more error-prone.

What is Ruff?

Ruff is the newest of the three, written in Rust and focused on speed and efficiency. It promises to be “orders of magnitude” faster than Pylint and Flake8, and a drop-in replacement for Flake8. It supports similar rulesets as Pylint but is not a one-to-one parity.

Use Cases and Specific Strengths of Each Tool

Ruff focuses on speed. It identifies common issues, supports a wide ruleset, and does it all very quickly. This makes it great for continuous integration (CI) systems and ideal for large projects. There are situations where Pylint and Flake8 will simply take too long to run on big projects that they become impractical.

Flake8 is best for projects needing both error and style checks, with lots of extensibility and configurability. While Ruff is a drop-in replacement for the base features of Flake8, it can integrate with flake8-bugbear to catch more issues that the alternatives cannot. With that said, its simplicity and relatively lacking performance means that it’s slowly being displaced by options like Ruff.

Pylint is Suitable for thorough code analysis. It checks for errors, style issues, and complex code smells. It examines the syntax tree not just for single files but across files. It checks for a wide range of issues, from syntax errors to code complexity. Pylint's detailed reports can help significantly improve code quality. Pylint is also the go-to choice for legacy code bases running Python 2, as Ruff doesn't support Python at all.

Comparison of Community Support and Updates

Ruff has recently developed but is rapidly gaining popularity. Active development means frequent updates and new features. It has a very active Discord community that you can visit to get quick responses or discuss feedback in depth.

Flake8 is a more established tool with a large user base. Its submodules still receive updates to ensure it stays current with Python standards. It also has a plugin system that makes it very versatile.

Pylint is a long-standing tool with extensive documentation. Regular updates and strong community support make it reliable. For many organizations with older reports, it’s impractical to adopt Ruff or Flake8 in place of Pylint because of the lack of feature parity, which makes it harder to enforce rules consistently.

What is the Difference Between Pylint and Ruff?

Understanding the differences between Pylint and Ruff can help you select the right tool for your project. Each offers distinct advantages tailored to different aspects of linting and code analysis.

Pylint's Focus on Comprehensive Code Analysis

Pylint is like a magnifying glass for your code. It examines every detail, finding errors, style issues, and even suggesting improvements. Pylint checks for:

  • Syntax Errors: Identifies mistakes that would cause your code to fail.

  • Style Issues: Enforces coding standards based on PEP 8.

  • Code Smells: Points out parts of the code that might lead to bugs or are hard to maintain.

  • Cross File Analysis: The ability to look across multiple files to catch errors like 

  • Basic Type Inference: While Pylint doesn’t have true type inference like MyPy or PyRight, it can still catch basic type errors.

  • Plugin System: Pylint supports “checkers” in the form of plugins to let you extend its capabilities. 

Pylint generates detailed reports, rating your code and highlighting areas needing attention. This thorough analysis helps maintain high code quality, especially in large and complex projects.

Ruff's Speed and Efficiency in Linting

Ruff, on the other hand, prioritizes speed. It quickly scans your code to catch common issues without slowing down your workflow. Ruff is exceptionally fast, making it ideal for:

  • Large Codebases: Handles thousands of lines of code efficiently. At certain sizes, Pylint become impractical to use, especially inline and pre-commit.

  • Continuous Integration Systems: Reduces linting time in CI pipelines, speeding up the development process.

  • Different Rulesets: Ruff supports a set of tools closer to Flake8. Ruff implements 800+ tools while Pylint implements ~400, 200 of which overlap. Like Flake8, Ruff can support McCabe's complexity checking and some other rules Pylint doesn’t have.

  • Active Community: Ruff has a lot of hype because it’s a Rust-based linter. You can get much faster responses to your questions from their active developer community.

  • Pylint Uses Ruff: Even the Pylint codebase uses Ruff for parts of its linting workflow.

Ruff's speed doesn't compromise its effectiveness. It supports many common linting rules, making it a powerful tool for rapid development cycles.

Differences in Rule Enforcement and Customization

Pylint and Ruff enforce rules differently:

  • Pylint: Offers extensive customization. You can enable or disable specific checks, configure thresholds, and tailor it to your coding style. This flexibility makes Pylint suitable for teams with strict coding standards.

  • Ruff: Focuses on essential linting rules. It covers a broad range of common issues but provides a different level of customization than Pylint. Ruff's straightforward approach keeps it fast and efficient.

How Ruff, Flake8, and Pylint Compare in Performance

Performance is a crucial factor when choosing a linter. Understanding how Ruff, Flake8, and Pylint stack up can help you pick the right tool.

Benchmarking Linting Speed of Ruff, Flake8, and Pylint

Speed tests reveal significant differences among Ruff, Flake8, and Pylint. Consider the following results from a real-world project with 120,000 lines of code from Itamar Turner-Trauring’s benchmarks:

  • Pylint: Takes approximately 14 seconds. Its detailed analysis comes at the cost of speed.

  • Flake8: Completes in about 1.7 seconds when combined with the Bugbear plugin. It leverages multiple cores, boosting performance.

  • Ruff: Finishes in just 0.2 seconds. This impressive speed makes Ruff ideal for large projects and rapid development cycles.

These benchmarks highlight Ruff's efficiency. It outperforms both Pylint and Flake8, making it a strong contender for speed-critical applications. When codebases grow to the order of millions of lines of code, Ruff becomes the only valid option to run frequently.

Analysis of Resource Usage for Each Tool

Resource usage varies between these linters. Here's a closer look:

  • Pylint: Uses more CPU and memory due to its comprehensive checks and runs as a single-threaded Python program. This can slow down your system, especially on large codebases.

  • Flake8: Moderately efficient, balancing performance and thoroughness. It uses multiple cores effectively, making it faster than Pylint.

  • Ruff: Optimized for minimal resource usage. Ruff is written in Rust, making it lightweight, and uses multiple threads to ensure it runs quickly.

Ruff's low resource consumption is a key advantage. It allows developers to lint large codebases without significant performance hits.

User Experiences and Anecdotal Evidence

Real-world user experiences provide valuable insights. Here’s what developers say about these tools:

  • Ruff: Praised for its speed. Developers report faster development cycles and improved productivity. Ideal for CI pipelines where time is critical.

  • Flake8: Valued for its balance of speed and thoroughness. Users appreciate its plugin system, which adds flexibility. Suitable for projects needing style and error checks.

  • Pylint: Known for its detailed analysis. Developers use it for code reviews and maintaining high code quality. However, some find it slow and resource-intensive.

Anecdotal evidence underscores the strengths and weaknesses of each tool. Ruff stands out for its speed, making it a favorite in fast-paced environments. Flake8 offers a good balance, while Pylint provides depth at the cost of performance.

Choosing between Ruff, Flake8, and Pylint depends on your specific needs. Consider speed, resource usage, project size, and user feedback to make an informed decision.

Why Use Flake8 for Style and Error Checking?

Flake8 is a popular linter for Python, offering a blend of error checking and style enforcement. Let's explore why Flake8 stands out for these tasks.

Flake8's Integration of Pyflakes, pycodestyle, and McCabe

Flake8 combines the strengths of Pyflakes and pycodestyle:

  • Pyflakes: Focuses on identifying syntax errors and undefined names. It ensures your code runs without basic errors.

  • Pycodestyle: Formerly known as pep8, it checks adherence to PEP 8, Python's style guide. This helps maintain a consistent coding style. It also enforces only a subset of Pylint, which is a slightly less strict set of rules.

  • Mccabe: Checks for complexity in code, warns when functions become overly complex, and suggests splitting up code for readability, testability, and maintainability.

Together, these tools provide a comprehensive linting solution, catching both errors and style issues.

Benefits of Using Flake8's Plugin System

Flake8's plugin system is one of its key advantages. You can extend its functionality by adding plugins tailored to your needs:

  • flake8-bugbear: Identifies common bugs and potential issues that Pyflakes might miss.

  • flake8-docstrings: Ensures your docstrings meet PEP 257 standards, improving documentation quality.

  • flake8-import-order: Checks the order of your imports, helping maintain a tidy and readable codebase.

These plugins allow you to customize Flake8 to fit your project's requirements, enhancing its utility. You can find a list of such plugins in this GitHub repo.

Does Ruff Replace Flake8?

Ruff is a drop-in replacement for Flake8 if you’re using Flake8 with a limited number of plugins. Where Ruff falls short is when you extend Flake8 with plugins or custom lint rules. Ruff doesn’t support extensions or plugins and has yet to support custom lint rules.

Ruff is still a compelling option if the plugins you need are one of these 52 plugins. Considering the speed of Ruff, it’s also possible to run Ruff alongside missing plugins.

What’s Missing From Pylint, Flake8, and Ruff?

Pylint, Flake8, and Ruff are not the end-all and all of Python linting and static analysis. They’re missing some key capabilities still:

  • Proper static type checking from tools such as MyPy and Pyright. These tools prevent bugs like passing a string argument to a function that expects a number.

  • Security tools like bandit and safety that look for known vulnerabilities.

  • Adjacent tools to lint and format JSON/YAML config files, Markdown docs, Docker and other IaC files, and other components to a modern repository.

Python is amazing for its flexibility, but the trade off is that large projects are much harder to maintain without tooling to check for type safety, vulnerability, and other common gotchas. This is why you’ll probably need to run more than just 1 or 2 static analysis tools.

What Should I Use on My Python Projects?

Python has a higher demand for linting and formatters than most languages. Poor formatting can quite literally break your code, and without static types and interpreting everything at runtime, it’s prone to errors. Regardless of your preferences between Pylint, Flake8, and Ruff, you should also run a static type checker and security tools to scan your code and infrastructure config.

So the answer, like for many other questions, is that it depends. You should use linter and formatters, and you’ll probably need to use more than one tool.

Comprehensive Linting for Python Projects

Python is flexible, which lets you work fast, but it needs many different linters, formatters, and static analysis tools as guardrails to keep you on track. Managing all of these linting tools and running them in a timely manner, however, is its own challenge.

Trunk Code Quality can help you run all the tools you need for your Python projects:

  • Runs only on the files and lines of code changed in your commit to save time

  • Hermetically installs linters and formatters for you and manages their runtime environments.

  • Share linter/formatter configuration across all your Python repos

  • Add custom linters and rules

  • Runs in VSCode, in CI, and the command line

Ready to elevate your linting experience for Python projects? Try Trunk Code Quality.

Try it yourself or
request a demo

Get started for free

Try it yourself or
Request a Demo

Free for first 5 users