# Does Ruff support type checking?

No. [Ruff](https://pydevtools.com/handbook/reference/ruff.md) is a Python linter and formatter; it does not perform type inference and is not a type checker. The [official Ruff FAQ](https://docs.astral.sh/ruff/faq/) states this directly: *"Ruff is a linter, not a type checker. It can detect some of the same problems that a type checker can, but a type checker will catch certain errors that Ruff would miss."*

For type checking, pair Ruff with a dedicated type checker. The handbook covers four in detail: [ty](https://pydevtools.com/handbook/reference/ty.md), [mypy](https://pydevtools.com/handbook/reference/mypy.md), [Pyright](https://pydevtools.com/handbook/reference/pyright.md), and [Pyrefly](https://pydevtools.com/handbook/reference/pyrefly.md). See [How do Python type checkers compare?](https://pydevtools.com/handbook/explanation/how-do-mypy-pyright-and-ty-compare.md) for the trade-offs.

## Why people think Ruff does type checking

A few features of Ruff and its ecosystem create the impression that it might do more than it does.

### Astral builds Ruff and ty as separate projects

[Astral](https://astral.sh) ships [Ruff](https://pydevtools.com/handbook/reference/ruff.md) and [ty](https://pydevtools.com/handbook/reference/ty.md) as separate projects with separate binaries. The two share infrastructure (the same Rust crate for parsing Python source, for example) but install and run independently. `ruff check` runs the linter; `ty check` runs the type checker. Installing Ruff does not give you type checking, and using ty does not require Ruff.

The naming and shared authorship create a reasonable expectation that "Astral's Python tool" handles everything, but the split is intentional. Linting and type checking are different categories of static analysis with different trade-offs around speed, configuration, and false positives.

### Ruff's ANN, TC, and PYI rules read annotation syntax

Several of Ruff's rule categories read type annotations as syntax:

- **ANN** (flake8-annotations): flags functions that are missing annotations on arguments or return values.
- **TC** (flake8-type-checking): identifies imports that are used only inside annotations and suggests moving them into `if TYPE_CHECKING:` blocks.
- **PYI** (flake8-pyi): enforces conventions for `.pyi` stub files.

These rules look at the text of the annotation, not at whether the annotation is correct. A function annotated `def add(a: int, b: int) -> str` and returning `a + b` passes every Ruff rule even though the return type is wrong. A type checker catches the mismatch; Ruff does not look at it.

### Pyflakes rules look like type checks but aren't

Ruff inherits the [pyflakes](https://github.com/PyCQA/pyflakes) rule set, which catches undefined names (`F821`), unused imports (`F401`), and unused variables (`F841`). These checks operate on the abstract syntax tree without doing type inference. They overlap with what a type checker would flag in some cases, which makes Ruff feel more "type-aware" than it actually is.

The difference: pyflakes notices that a name was never assigned. A type checker notices that an assigned name has the wrong type.

## What a type checker catches that Ruff misses

A concrete example shows the gap:

```python
def parse_count(raw: str) -> int:
    return raw.strip()  # bug: .strip() returns str, not int

total: int = parse_count("  42  ")
print(total + 1)  # TypeError at runtime
```

Ruff reports no errors on this code. Every name is defined, every import is used, every annotation parses. The function declares it returns `int`, and that declaration is syntactically valid.

A type checker walks the function body, infers that `raw.strip()` returns `str`, and reports the type error before runtime:

```text
$ ty check parse.py
error[invalid-return-type]: Return type does not match returned value
 --> parse.py:1:30
  |
1 | def parse_count(raw: str) -> int:
  |                              --- Expected `int` because of return type
2 |     return raw.strip()
  |            ^^^^^^^^^^^ expected `int`, found `str`
```

[mypy](https://pydevtools.com/handbook/reference/mypy.md), [Pyright](https://pydevtools.com/handbook/reference/pyright.md), and [Pyrefly](https://pydevtools.com/handbook/reference/pyrefly.md) report the same mismatch with slightly different output formats. Catching this category of bug requires tracing values through expressions, which is what type inference does and what Ruff does not do.

## Pair Ruff with a dedicated type checker

For new projects, the handbook recommends running Ruff for lint and format, and one of the four major type checkers for type analysis:

- **[ty](https://pydevtools.com/handbook/reference/ty.md)** (Astral, Rust, beta as of 2026): fast, prioritizes inference on unannotated code, integrates with the Astral toolchain.
- **[mypy](https://pydevtools.com/handbook/reference/mypy.md)** (community, Python, 1.0 since 2023): the original Python type checker; widest plugin ecosystem.
- **[Pyright](https://pydevtools.com/handbook/reference/pyright.md)** (Microsoft, TypeScript, available since 2019): the engine behind Pylance; strongest VS Code integration.
- **[Pyrefly](https://pydevtools.com/handbook/reference/pyrefly.md)** (Meta, Rust, 1.0 in May 2026): fast, high spec-conformance, used at Instagram and PyTorch.

[How do Python type checkers compare?](https://pydevtools.com/handbook/explanation/how-do-mypy-pyright-and-ty-compare.md) walks through the trade-offs (speed, conformance, strictness defaults, licensing). For a step-by-step playbook on adding type checking to an existing codebase, see [How to gradually adopt type checking in an existing Python project](https://pydevtools.com/handbook/how-to/how-to-gradually-adopt-type-checking-in-an-existing-python-project.md).

A common project setup runs all of these tools at different stages: Ruff on save, the type checker on commit (via [prek](https://pydevtools.com/handbook/reference/prek.md)), and both in CI.

## Learn More

* [Ruff FAQ: How does Ruff compare to Mypy, or Pyright, or Pyre?](https://docs.astral.sh/ruff/faq/)
* [Ruff rule reference](https://docs.astral.sh/ruff/rules/)
* [How do Python type checkers compare?](https://pydevtools.com/handbook/explanation/how-do-mypy-pyright-and-ty-compare.md)
* [Ruff: A Complete Guide to Python's Fastest Linter and Formatter](https://pydevtools.com/handbook/explanation/ruff-complete-guide.md)
* [How to gradually adopt type checking in an existing Python project](https://pydevtools.com/handbook/how-to/how-to-gradually-adopt-type-checking-in-an-existing-python-project.md)
