Published on

Python Type Hints - Write Cleaner, Safer Code

Authors

Introduction

Python type hints are no longer optional — they're a professional standard. They make your code self-documenting, catch bugs before runtime, and power popular libraries like FastAPI and Pydantic.

If you're writing Python without type hints in 2026, you're missing out on one of the language's most powerful features.

Basic Type Hints

main.py
# Without type hints
def greet(name):
    return "Hello, " + name

# With type hints
def greet(name: str) -> str:
    return "Hello, " + name

The syntax is: variable: type for parameters and -> type after the function signature for return types.

Built-in Types

main.py
# Basic types
x: int = 10
name: str = "Alice"
price: float = 9.99
is_active: bool = True

# Function with multiple types
def add(a: int, b: int) -> int:
    return a + b

def get_full_name(first: str, last: str) -> str:
    return f"{first} {last}"

Collections: List, Dict, Tuple, Set

main.py
from typing import List, Dict, Tuple, Set  # Python 3.8 and below

# Python 3.9+ — you can use built-in generics directly
def get_names() -> list[str]:
    return ["Alice", "Bob", "Charlie"]

def get_scores() -> dict[str, int]:
    return {"Alice": 95, "Bob": 88}

def get_coordinates() -> tuple[float, float]:
    return (40.7128, -74.0060)

def get_unique_tags() -> set[str]:
    return {"python", "coding", "tech"}

Optional Types

Use Optional when a value can be None:

main.py
from typing import Optional

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

# Python 3.10+ shorthand using Union with None
def find_user_v2(user_id: int) -> str | None:
    users = {1: "Alice", 2: "Bob"}
    return users.get(user_id)

Union Types

main.py
from typing import Union

def process_id(id: Union[int, str]) -> str:
    return str(id)

# Python 3.10+ shorthand
def process_id_v2(id: int | str) -> str:
    return str(id)

TypedDict for Structured Dictionaries

main.py
from typing import TypedDict

class User(TypedDict):
    name: str
    age: int
    email: str

def create_user(name: str, age: int, email: str) -> User:
    return {"name": name, "age": age, "email": email}

user = create_user("Alice", 30, "alice@example.com")
print(user["name"])  # Type-safe access!

Dataclasses — Type Hints + Auto-Generated Methods

main.py
from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: float
    in_stock: bool = True

    def discount(self, percent: float) -> float:
        return self.price * (1 - percent / 100)

p = Product(name="Laptop", price=999.99)
print(p.discount(10))  # 899.991

Dataclasses automatically generate __init__, __repr__, and __eq__ based on your type hints.

Callable Types

main.py
from typing import Callable

def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

result = apply(lambda x, y: x + y, 5, 3)
print(result)  # 8

Generic Types

main.py
from typing import TypeVar, Generic, List

T = TypeVar('T')

class Stack(Generic[T]):
    def __init__(self) -> None:
        self.items: List[T] = []

    def push(self, item: T) -> None:
        self.items.append(item)

    def pop(self) -> T:
        return self.items.pop()

stack: Stack[int] = Stack()
stack.push(1)
stack.push(2)
print(stack.pop())  # 2

Using mypy for Static Type Checking

Install mypy to catch type errors before runtime:

pip install mypy
buggy.py
def add(a: int, b: int) -> int:
    return a + b

result = add("hello", 5)  # Bug!
mypy buggy.py
# Error: Argument 1 to "add" has incompatible type "str"; expected "int"

Mypy catches the bug without running the code!

Real-World Example: Type-Hinted API Service

user_service.py
from typing import Optional
from dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str
    email: str
    is_admin: bool = False

class UserService:
    def __init__(self) -> None:
        self.users: dict[int, User] = {}

    def create(self, name: str, email: str) -> User:
        new_id = len(self.users) + 1
        user = User(id=new_id, name=name, email=email)
        self.users[new_id] = user
        return user

    def find_by_id(self, user_id: int) -> Optional[User]:
        return self.users.get(user_id)

    def find_admins(self) -> list[User]:
        return [u for u in self.users.values() if u.is_admin]

Conclusion

Type hints transform Python from a loosely-typed scripting language into a robust, self-documenting codebase. They enable better IDE autocomplete, catch bugs early with mypy, and are required by major frameworks like FastAPI and Pydantic. Start adding them to your code today — future you will thank you.