> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi2day.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Python Type Hints: Annotate Variables and Functions

> Learn how to use Python type hints for variables, functions, and collections, and how Pydantic and FastAPI use them for runtime validation.

Type hints are one of Python's most practical features for writing clear, maintainable code. They tell you — and your tools — what type of data a variable, parameter, or function should carry. While Python's runtime ignores them completely (they're never enforced by the interpreter), they are actively read and used by your IDE, static analysis tools like `mypy`, and validation libraries like **Pydantic** — which is at the heart of FastAPI. Understanding type hints is a prerequisite for working effectively with Pydantic models and FastAPI routes.

## Basic Variable Type Hints

You annotate a variable using a colon followed by the type:

```python theme={null}
name:      str   = "Alice"
age:       int   = 30
price:     float = 19.99
is_active: bool  = True
```

The `: str`, `: int`, `: float`, and `: bool` parts are type hints — they document intent but do not enforce anything at runtime.

## Python Doesn't Enforce Type Hints

This is the most important thing to understand upfront:

```python theme={null}
age: int = "not a number"   # Python allows this — no error
```

Type hints are **annotations**, not constraints. The Python interpreter runs this code without complaint. They exist for three purposes:

1. **Documentation** — code becomes self-explanatory.
2. **IDE support** — autocomplete, inline error detection, and refactoring work better.
3. **Validation tools** — Pydantic, mypy, and others read and act on them.

```python theme={null}
# Without type hints — what are the expected types?
def create_user(name, email, age):
    pass

# With type hints — crystal clear
def create_user(name: str, email: str, age: int) -> dict:
    pass
```

## Basic Types

The four types you'll annotate most often:

```python theme={null}
# str — text
name: str = "Alice"
message: str = "Hello, world!"

# int — whole numbers
count: int = 42
user_id: int = 1001

# float — decimal numbers
price: float = 29.99
temperature: float = 98.6

# bool — True or False
is_active: bool = True
has_access: bool = False
```

## Container Types

For collections, you specify the type of their contents:

```python theme={null}
# List of strings
tags: list[str] = ["python", "pydantic", "fastapi"]

# List of integers
quantities: list[int] = [1, 5, 3, 2]

# Dictionary with string keys and integer values
word_counts: dict[str, int] = {"error": 12, "warning": 5}

# Dictionary with string keys and string values
settings: dict[str, str] = {"theme": "dark", "language": "en"}

# Nested: list of dictionaries
users: list[dict[str, str]] = [{"name": "Alice"}, {"name": "Bob"}]
```

<Note>
  Python 3.9+ supports lowercase built-in types (`list`, `dict`, `set`, `tuple`) directly in annotations. Older code imports uppercase equivalents from `typing` (`List`, `Dict`). They are equivalent — prefer the lowercase built-in syntax in new code.
</Note>

## Optional Values

When a value might be `None`, use `Optional` from `typing` or the `|` union syntax (Python 3.10+):

```python theme={null}
from typing import Optional

# These three are equivalent
middle_name: Optional[str] = None
middle_name: str | None = None

# Practical examples
phone: str | None = None
phone = "+1-555-0123"   # valid assignment
phone = None            # also valid
```

## Literal Types

When a variable must be one of a fixed set of values, use `Literal`:

```python theme={null}
from typing import Literal

status: Literal["draft", "published", "archived"] = "draft"

# Type checkers warn if you assign an unlisted value
log_level: Literal["debug", "info", "warning", "error"] = "info"
priority:  Literal["low", "medium", "high"]              = "medium"
```

## Function Type Hints

Type hints on function parameters and return values are especially valuable:

```python theme={null}
def format_price(amount: float, currency: str = "USD") -> str:
    return f"{currency} {amount:.2f}"

def calculate_total(prices: list[float], tax_rate: float) -> float:
    subtotal = sum(prices)
    return subtotal * (1 + tax_rate)

def get_config(key: str) -> str | None:
    # Returns the value if found, None if the key doesn't exist
    pass
```

The `-> str` after the closing parenthesis declares the **return type**.

## Common Patterns

```python theme={null}
from typing import Optional, Literal

# Required string (no default)
name: str

# Optional string — can be None
nickname: str | None = None

# String with a default value
country: str = "USA"

# List of items — starts empty
items: list[str] = []

# Dictionary — starts empty
metadata: dict[str, str] = {}

# Specific allowed values
role: Literal["admin", "user", "guest"] = "user"
```

## Type Hints Don't Validate — Pydantic Does

Remember: Python's runtime ignores type hints entirely:

```python theme={null}
age: int = "not a number"           # No error
prices: list[float] = "not a list"  # No error
```

This is exactly where **Pydantic** comes in. Pydantic reads your type hints at class-definition time and generates real validators that run when objects are created:

```python theme={null}
from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int

u = User(name="Alice", age="25")   # "25" is coerced to int 25
print(u.age)         # 25
print(type(u.age))   # <class 'int'>

# Invalid data raises a clear ValidationError
u2 = User(name="Bob", age="not-a-number")
# pydantic_core.ValidationError: 1 validation error for User
# age: Input should be a valid integer [...]
```

FastAPI uses Pydantic models for all request and response bodies, so your type hints automatically become validation rules, serialisation schemas, and OpenAPI documentation — all at once.
