mypy Incompatible types in assignment (tested)

mypy reports Incompatible types in assignment (expression has type "str", variable has type "int") when you assign a value whose type does not match the type the variable already has, usually inferred from its first assignment. Declare the wider type up front (x: int | str = 0) or narrow the value first.

1. The four common causes

b1.py:

from typing import Optional

count = 0
count = "zero"          # int variable, str value

def find(name: str) -> Optional[int]:
    return None if name == "" else len(name)

n: int = find("abc")    # Optional[int] into int

prices = [1, 2, 3]
prices = [1.5, 2.5]     # list[int] variable, list[float] value

total = 0
total += 0.5            # int += float

Output (mypy 2.4.0, Python 3.13.5):

$ mypy --no-incremental b1.py
b1.py:4: error: Incompatible types in assignment (expression has type "str", variable has type "int")  [assignment]
b1.py:9: error: Incompatible types in assignment (expression has type "int | None", variable has type "int")  [assignment]
b1.py:12: error: List item 0 has incompatible type "float"; expected "int"  [list-item]
b1.py:12: error: List item 1 has incompatible type "float"; expected "int"  [list-item]
b1.py:15: error: Incompatible types in assignment (expression has type "float", variable has type "int")  [assignment]
Found 5 errors in 1 file (checked 1 source file)

2. The fixes

from typing import Optional

count: int | str = 0
count = "zero"

def find(name: str) -> Optional[int]:
    return None if name == "" else len(name)

found = find("abc")
n: int = found if found is not None else 0

prices: list[float] = [1, 2, 3]
prices = [1.5, 2.5]

total = 0.0
total += 0.5
$ mypy --no-incremental b2.py
Success: no issues found in 1 source file

Declare the wider type up front (int | str, list[float]), start from the right type (0.0), or narrow the Optional with an is not None check before assigning.

3. Default None and reusing a name

def load(path: str = None) -> str:
    return path or "default.txt"

def parse(raw):
    value = raw.strip()
    value = int(value)      # redefine with a new type
    return value

def parse2(raw: str) -> int:
    value = raw.strip()
    value = int(value)
    return value
$ mypy --no-incremental b3.py
b3.py:1: error: Incompatible default for parameter "path" (default has type "None", parameter has type "str")  [assignment]
b3.py:1: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
b3.py:1: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
b3.py:11: error: Incompatible types in assignment (expression has type "int", variable has type "str")  [assignment]
b3.py:12: error: Incompatible return value type (got "str", expected "int")  [return-value]
Found 3 errors in 1 file (checked 1 source file)
$ mypy --no-incremental --allow-redefinition b3.py
b3.py:1: error: Incompatible default for parameter "path" (default has type "None", parameter has type "str")  [assignment]
b3.py:1: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
b3.py:1: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
Found 1 error in 1 file (checked 1 source file)

To silence one line, use # type: ignore[assignment]; prefer fixing the type.

Frequently asked questions

Why does mypy say incompatible types in assignment?

The variable got a type at its first assignment (or annotation) and the new value has a different type, e.g. count = 0 then count = "zero".

How do I fix expression has type "int | None", variable has type "int"?

Check for None before assigning (x if x is not None else 0) or annotate the variable as int | None.

What does --allow-redefinition do?

It lets a variable be re-bound with a different type within the same block, e.g. value = raw.strip() then value = int(value).

Written with AI assistance. Measured on 2026-10-23 with mypy 2.4.0 and Python 3.13.5 on Linux. Files were checked verbatim with --no-incremental and the output pasted unedited. PlainTables is our extension; mypy is free.