Python's dynamic typing is flexible but can lead to runtime errors and unclear APIs. The typing module enables static type checking, better IDE support, and self-documenting code without runtime performance impact, catching bugs before they reach production.

Why Use Type Hints?

Generic Types

Use TypeVar and Generic to create type-safe generic functions and classes.

basic_hints.py
# Basic type hints for functions and variables

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}!"

def is_adult(age: int) -> bool:
    return age >= 18

# Variable type hints
username: str = 
age: int = 
height: float = 5.8
is_active: bool = True

# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")

message = greet("Bob")
print(message)

print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}!"

def is_adult(age: int) -> bool:
    return age >= 18

# Variable type hints
username: str = 
age: int = 
height: float = 5.8
is_active: bool = True

# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")

message = greet("Bob")
print(message)

print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}!"

def is_adult(age: int) -> bool:
    return age >= 18

# Variable type hints
username: str = 
age: int = 
height: float = 5.8
is_active: bool = True

# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")

message = greet("Bob")
print(message)

print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}!"

def is_adult(age: int) -> bool:
    return age >= 18

# Variable type hints
username: str = 
age: int = 
height: float = 5.8
is_active: bool = True

# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")

message = greet("Bob")
print(message)

print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}!"

def is_adult(age: int) -> bool:
    return age >= 18

# Variable type hints
username: str = 
age: int = 
height: float = 5.8
is_active: bool = True

# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")

message = greet("Bob")
print(message)

print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}!"

def is_adult(age: int) -> bool:
    return age >= 18

# Variable type hints
username: str = 
age: int = 
height: float = 5.8
is_active: bool = True

# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")

message = greet("Bob")
print(message)

print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}!"

def is_adult(age: int) -> bool:
    return age >= 18

# Variable type hints
username: str = 
age: int = 
height: float = 5.8
is_active: bool = True

# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")

message = greet("Bob")
print(message)

print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}!"

def is_adult(age: int) -> bool:
    return age >= 18

# Variable type hints
username: str = 
age: int = 
height: float = 5.8
is_active: bool = True

# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")

message = greet("Bob")
print(message)

print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}!"

def is_adult(age: int) -> bool:
    return age >= 18

# Variable type hints
username: str = 
age: int = 
height: float = 5.8
is_active: bool = True

# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")

message = greet("Bob")
print(message)

print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
collections.py
# Collection type hints

from typing import List, Dict, Set, Tuple

def process_numbers(numbers: List[int]) -> int:
    return sum(numbers)

def get_scores() -> Dict[str, int]:
    return {"Alice": 95, "Bob": 87, "Charlie": 92}

def unique_values(values: List[int]) -> Set[int]:
    return set(values)

def get_coordinates() -> Tuple[float, float, float]:
    return (10.5, 20.3, 30.7)

# Usage
nums = [1, 2, 3, 4, 5]
total = process_numbers(nums)
print(f"Sum: {total}")

scores = get_scores()
print(f"Scores: {scores}")

unique = unique_values([1, 2, 2, 3, 3, 3])
print(f"Unique: {unique}")

x, y, z = get_coordinates()
print(f"Coordinates: ({x}, {y}, {z})")
optional.py
# Optional type for nullable values

from typing import Optional

def find_user(user_id: int) -> Optional[str]:
    """Returns username or None if not found"""
    users = {1: "Alice", 2: "Bob", 3: "Charlie"}
    return users.get(user_id)

def divide(a: float, b: float) -> Optional[float]:
    """Returns result or None if division by zero"""
    if b == 0:
        return None
    return a / b

# Usage
user = find_user(2)
if user is not None:
    print(f"Found user: {user}")
else:
    print("User not found")

user = find_user(999)
if user is not None:
    print(f"Found user: {user}")
else:
    print("User not found")

# Division
result = divide(10, 2)
print(f"10 / 2 = {result}")

result = divide(10, 0)
print(f"10 / 0 = {result}")
union.py
# Union types for multiple allowed types

from typing import Union

def format_value(value: Union[int, float, str]) -> str:
    """Format different types of values"""
    if isinstance(value, (int, float)):
        return f"Number: {value}"
    return f"Text: {value}"

def process_id(id_value: Union[int, str]) -> str:
    """Process ID that can be int or string"""
    return f"ID-{id_value}"

# Usage
print(format_value(42))
print(format_value(3.14))
print(format_value("hello"))

print(process_id(1001))
print(process_id("ABC123"))

# Function that returns different types
def get_config(key: str) -> Union[str, int, bool]:
    config = {
        "host": "localhost",
        "port": 8080,
        "debug": True
    }
    return config.get(key, "")

print(f"Host: {get_config('host')}")
print(f"Port: {get_config('port')}")
print(f"Debug: {get_config('debug')}")
callable.py
# Callable type for function parameters

from typing import Callable, List

def apply_operation(numbers: List[int], operation: Callable[[int], int]) -> List[int]:
    """Apply operation to each number"""
    return [operation(n) for n in numbers]

def filter_values(numbers: List[int], predicate: Callable[[int], bool]) -> List[int]:
    """Filter numbers using predicate function"""
    return [n for n in numbers if predicate(n)]

# Define operations
def double(x: int) -> int:
    return x * 2

def square(x: int) -> int:
    return x * x

def is_even(x: int) -> bool:
    return x % 2 == 0

# Usage
numbers = [1, 2, 3, 4, 5]

doubled = apply_operation(numbers, double)
print(f"Doubled: {doubled}")

squared = apply_operation(numbers, square)
print(f"Squared: {squared}")

evens = filter_values(numbers, is_even)
print(f"Even numbers: {evens}")

# Using lambda
odds = filter_values(numbers, lambda x: x % 2 != 0)
print(f"Odd numbers: {odds}")
generics.py
# Generic types with TypeVar

from typing import TypeVar, List, Generic

T = TypeVar('T')

def get_first(items: List[T]) -> T:
    """Get first item from list, preserving type"""
    return items[0]

def get_last(items: List[T]) -> T:
    """Get last item from list, preserving type"""
    return items[-1]

# Generic class
class Stack(Generic[T]):
    def __init__(self):
        self._items: List[T] = []
    
    def push(self, item: T) -> None:
        self._items.append(item)
    
    def pop(self) -> T:
        return self._items.pop()
    
    def is_empty(self) -> bool:
        return len(self._items) == 0

# Usage with different types
numbers = [1, 2, 3, 4, 5]
print(f"First number: {get_first(numbers)}")
print(f"Last number: {get_last(numbers)}")

words = ["hello", "world", "python"]
print(f"First word: {get_first(words)}")
print(f"Last word: {get_last(words)}")

# Generic stack
int_stack: Stack[int] = Stack()
int_stack.push(10)
int_stack.push(20)
int_stack.push(30)
print(f"Popped: {int_stack.pop()}")

str_stack: Stack[str] = Stack()
str_stack.push("a")
str_stack.push("b")
print(f"Popped: {str_stack.pop()}")

@seealso dataclass_intro "Dataclasses with type hints" @seealso namedtuple_intro "Typed NamedTuples"

type hint An annotation that specifies the expected type of a variable, function parameter, or return value, enabling static analysis and better documentation.
Optional A type hint indicating a value can be either the specified type or None, equivalent to Union[T, None].
Union A type hint that accepts any of the specified types, useful when a value can legitimately be different types.
Callable A type hint for function parameters or variables that hold callable objects, specifying the expected parameter types and return type.
Generic A base class for creating generic types that work with any type while maintaining type safety, using TypeVar for type parameters.