Python Never Type: Functions That Never Return
python never type: Learn how to use Python's Never type to annotate functions that raise, exit, or loop forever, and how it improves static type checking.
The Never type in Python, written as typing.Never, marks functions that never return a value to the caller. A function annotated with Never either raises an exception, calls sys.exit(), or enters an infinite loop. This python never type hint is consumed by static type checkers like mypy and Pyright; at runtime it has no effect on function behavior.
What Never Means in Python Type Hints
Never is the bottom type in Python's type system. It is a subtype of every other type, and it has no members. No value can actually be assigned to a variable of type Never. The only place a value of type Never exists is in the control-flow analysis performed by a type checker, where it represents an unreachable branch.
When a function is annotated with -> Never, the type checker assumes the function call never completes normally. That assumption lets the checker reason about code that follows the call:
from typing import Never def fail(message: str) -> Never: raise RuntimeError(message) def process(data: bytes) -> None: if not data: fail("empty input") # The type checker knows `data` is non-empty here. ...
Because fail returns Never, the type checker treats the if branch as terminating the function. Code after the branch is analyzed under the assumption that the condition was false. Without the Never annotation, the checker would have to assume fail could return normally, and data could still be empty.
Never vs NoReturn: What Each One Is For
Python has two annotations that describe functions that never return: Never and NoReturn. They are equivalent in the type checker's view, but they were designed for different purposes.
| Aspect | Never | NoReturn |
|---|---|---|
Added to typing | Python 3.11 | Python 3.6.2 |
| Primary intent | Bottom type for any position | Return annotation for never-returning functions |
| Valid in generic arguments | Yes, e.g. list[Never] | Not intended for that use |
| Static checker semantics | Same as NoReturn | Same as Never |
NoReturn was introduced first and is the more familiar name in existing codebases. Never was added later to give the same concept a name that works naturally in generic positions, such as list[Never], which describes an empty list that can never contain a value.
For a function return annotation, the two are interchangeable. mypy, Pyright, and other major checkers treat -> Never and -> NoReturn identically. The choice is mostly about readability and consistency with the rest of your type hints.
Using Never for Functions That Never Return
The most common use of Never is to annotate a function that always raises or always exits the process.
from typing import Never import sys def exit_with_error(code: int) -> Never: sys.exit(code) def validate(value: int | None) -> int: if value is None: exit_with_error(1) return value
The type checker understands that exit_with_error does not return. After the if branch, value is narrowed to int, and the function can safely return it. If exit_with_error were annotated with None instead, the checker would complain that value could still be None when the function returns.
This pattern is useful for helper functions that centralize error handling. Instead of repeating raise statements, you can call a helper that raises, and the type checker still understands that the call never produces a value.
Exhaustive Type Checks With Never
Never also appears in type narrowing, specifically in exhaustive checks over unions. The assert_never function from typing is the standard tool.
from typing import Never, assert_never class Circle: radius: float class Square: side: float Shape = Circle | Square def area(shape: Shape) -> float: if isinstance(shape, Circle): return 3.14 * shape.radius ** 2 if isinstance(shape, Square): return shape.side ** 2 assert_never(shape)
assert_never is annotated to return Never, and it expects its argument to be of type Never. If the type checker can prove that shape has been narrowed to Never at that point, the call is valid. If a new class is added to the Shape union later, the checker will report that shape is not Never at the assert_never call, which forces you to handle the new case.
The same idea works with match statements. After all cases are handled, the final case _ branch can call assert_never.
Runtime Behavior: Why Never Is Not a Real Type
Never is a static-analysis construct. At runtime, typing.Never is a special form, not a class. You cannot use it with isinstance() or issubclass().
from typing import Never isinstance(1, Never) # TypeError
Attempting either call raises TypeError. The same is true for NoReturn. This is not a bug; it is the intended design. Never exists to communicate unreachable code to the type checker, and no value can ever have that type at runtime.
If you call typing.get_type_hints() on a function annotated with Never, the result will contain typing.Never (or typing.NoReturn, depending on how the annotation was written). This is useful for tooling that inspects annotations, but the value is not usable as a runtime type.
Common Mistakes and Edge Cases
One common mistake is annotating a function with Never when it can actually return in some cases.
from typing import Never def parse(value: str) -> Never: # Wrong if value == "error": raise ValueError(value) return int(value)
The type checker will reject this because the function has a path that returns a value. Never is only valid when every path raises, exits, or loops forever.
Another edge case is a function that loops forever. A type checker accepts it as Never because the function never reaches a return statement.
from typing import Never def poll() -> Never: while True: ...
A subtle case is generator functions. A generator function annotated with -> Never is still a generator; calling it returns a generator object instead of executing the body. The annotation describes the return type of the generator function itself, not the values it yields. For a generator that never yields, the annotation is misleading, and Never is not the right tool.
Compatibility and Migration Considerations
typing.Never was added in Python 3.11. If your project supports older Python versions, you have two options: use typing.NoReturn (available since 3.6.2) for return annotations, or install typing_extensions and import Never from there.
try: from typing import Never except ImportError: from typing_extensions import Never
The typing_extensions package provides the same Never type for Python 3.7 and earlier. Static type checkers handle it identically regardless of the import source.
When migrating an existing codebase from NoReturn to Never, the change is mechanical for return annotations. The two are interchangeable in that position. The main reason to switch is consistency if you also use Never in generic positions, such as list[Never] or dict[str, Never]. Those usages are not valid with NoReturn because NoReturn is not intended to be used as a type argument.