Using List/Tuple/etc. from typing vs directly referring type as list/tuple/etc
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Before Python 3.9, you had to import List, Dict, Tuple, Set, etc. from the typing module to use generic type hints (e.g., List[int]). Since Python 3.9, the built-in types list, dict, tuple, set support subscripting directly (e.g., list[int]), making the typing imports unnecessary for most cases. The typing versions still exist for backward compatibility but are considered legacy. This article explains when to use which, the migration path, and the remaining cases where typing is still needed.
Before Python 3.9
List[int] is typing.List subscripted with int. Without the import, list[int] raises TypeError in Python 3.8 and earlier.
Python 3.9+: Built-in Generics
Since Python 3.9, list[int], dict[str, int], tuple[str, ...], and set[int] work directly. Since Python 3.10, X | None replaces Optional[X].
Using from __future__ import annotations (Python 3.7+)
With from __future__ import annotations, annotations are not evaluated at runtime — they become strings. This lets you use the modern list[int] syntax on Python 3.7+.
Comparison Table
| Type Hint | typing (pre-3.9) | Built-in (3.9+) | |
| List of ints | List[int] | list[int] | |
| Dictionary | Dict[str, int] | dict[str, int] | |
| Tuple (fixed) | Tuple[str, int] | tuple[str, int] | |
| Tuple (variable) | Tuple[int, ...] | tuple[int, ...] | |
| Set | Set[str] | set[str] | |
| Frozen set | FrozenSet[int] | frozenset[int] | |
| Optional | Optional[int] | `int \ | None` (3.10+) |
| Union | Union[int, str] | `int \ | str` (3.10+) |
| Type | Type[MyClass] | type[MyClass] |
When typing Is Still Needed
Some constructs have no built-in equivalent:
Migration Example
The migration is purely syntactic — runtime behavior is identical.
Runtime vs Static Type Checking
Common Pitfalls
- Using
list[int]on Python 3.8 without__future__annotations:list[int]raisesTypeErrorat runtime on Python 3.8 and earlier. Either import fromtypingor addfrom __future__ import annotationsat the top of the file. - Mixing
typing.Listandlistin the same codebase: UsingList[int]in some files andlist[int]in others is inconsistent. Pick one style based on your minimum Python version and apply it consistently. - Confusing
Tuple[int, int]withTuple[int, ...]:tuple[int, int]is a fixed-length tuple of exactly two ints.tuple[int, ...]is a variable-length tuple of ints. Omitting the...means fixed length, which may not be what you intended. - Assuming type hints enforce types at runtime: Python does not check type hints during execution.
def f(x: int): ...accepts any argument type. Usemypyorpyrightfor static checking, andisinstance()for runtime validation. - Importing deprecated
typingaliases on Python 3.9+:typing.List,typing.Dict, etc. are deprecated since Python 3.9 and may be removed in future versions. Use the built-in types directly to avoid deprecation warnings in type checkers.
Summary
- Use
list[int],dict[str, int],tuple[str, ...]directly on Python 3.9+ — notypingimports needed - Use
from __future__ import annotationsto enable the modern syntax on Python 3.7-3.8 typing.List,typing.Dict, etc. are deprecated since 3.9 — use built-in types in new codetypingis still needed forTypeVar,Generic,Protocol,Callable,Literal, and other advanced constructs- Use
X | Noneinstead ofOptional[X]on Python 3.10+ (or with__future__annotations on 3.7+) - Type hints are for static analysis tools — Python does not enforce them at runtime

