Python is dynamically typed, meaning you never have to declare what type a variable holds — the interpreter figures it out at runtime. That flexibility is great for quick scripts, but on a larger codebase it can make it hard to tell, just by reading a function signature, what kind of arguments it expects or what it returns. Type hints (also called type annotations) solve that readability problem by letting you optionally write the expected types directly into your code.

It's important to understand what type hints are not: they are not enforced by the Python interpreter. You can annotate a parameter as int and still pass it a string, and your program will run exactly as if the hint weren't there. Type hints are documentation-as-code, checked by external tools, not by Python itself.

Basic Variable and Function Annotations

# Variable annotation — mostly useful for documentation, Python ignores it at runtime
age: int = 25
name: str = "Asha"
price: float = 19.99

# Function annotations: parameter types, and -> marks the return type
def greet(name: str, times: int) -> str:
    return (name + "! ") * times

print(greet("Hello", 2))

Output:

Hello! Hello!

The -> str after the parameter list tells readers (and tools) that greet is expected to return a string. If you accidentally returned an integer instead, Python itself wouldn't complain — but a type checker would flag it immediately.

Hinting Collections: list, dict, and Optional

Modern Python (3.9+) lets you parameterize built-in collection types directly, without importing anything extra:

from typing import Optional

def average(numbers: list[float]) -> float:
    return sum(numbers) / len(numbers)

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

print(average([10.0, 20.0, 30.0]))
print(find_user(1))
print(find_user(99))

Output:

20.0
Asha
None

Optional[str] means "either a str, or None" — it's shorthand for Union[str, None]. This is a very common pattern for functions like dictionary lookups, database queries, or anything that might legitimately find nothing.

Why Bother, If Python Doesn't Enforce Them?

Three reasons teams adopt type hints even though they're optional:

1. Editor support. Tools like VS Code and PyCharm read your hints to offer accurate autocomplete and catch obvious mistakes as you type, before you even run the code.
2. Self-documenting functions. A hinted signature like def process(items: list[dict]) -> bool: tells a reader far more at a glance than an unhinted one.
3. Static type checkers. A separate tool called mypy (run as mypy your_file.py) reads your hints and reports type mismatches — catching a whole category of bugs before you ever run the program.

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

add("2", "3")  # Python runs this fine (string concatenation), but mypy would flag it as a type error

When to Use Type Hints

For a short throwaway script, type hints are usually overkill. For anything shared with a team, published as a library, or likely to be maintained for more than a few weeks, they pay for themselves quickly — especially on functions with several parameters, where the hints remove any ambiguity about what's expected. Think of them as an investment in your future self, not a requirement the language forces on you.