Learn by reading through in order

Type Hints — Leave Your Intent in the Code with : int and -> str

Learn Python type hints. The def f(x: int) -> str: function signature, when to add variable annotations and when not to, and how IDEs use them for static checks — hands-on.

This article covers type hints — a tool for leaving "what goes here" intent inside the code. Introduced in Python 3.5, they've grown more expressive with each release.

The "What's the Type?" Problem in a Dynamic Language

Python is a dynamically typed language. Write value = 10 and it's int; write value = "HELLO" and the same name is now str. The same variable can flip type at any time — that's flexibility. The downside is that as your codebase grows or a team starts touching it, "what does this variable hold?" / "what does this function return?" becomes unanswerable from the code alone.

Type hints leave that information as annotations. They don't change runtime behavior. What they do is give human readers a strong hint and let IDEs and static analyzers catch type mistakes ahead of time.

Function without hints vs with hints
calc_tax(price, rate)what to pass?what returns?wrong guess-> bugcalc_tax(price: int, rate: float)-> intintent readsfrom the typeIDE (VS Code)warns to help
Without hints, callers have to guess what price should be. With hints, the function signature alone tells you the input and output types.

Annotating Function Signatures

Function signatures are the main stage for type hints. Annotate arguments as name: type (colon then type after the name) and the return type as -> type (arrow then type at the end of the def line).

Anatomy of a function signature
def calc_tax(price: intrate: float) -> int:argument hint(expects int)argument hint(expects float)return hint(returns int)
The colon after the argument name declares the argument's type, and the arrow -> after ) declares the return type. Colon-paired = argument annotation; arrow-paired = return annotation.
# ❌ no hints — the signature doesn't tell you what to pass
def calc_tax(price, rate):
    return int(price * (1 + rate))


# ✅ with hints — "int in, int out" is obvious
def calc_tax(price: int, rate: float) -> int:
    return int(price * (1 + rate))


print(calc_tax(price=1000, rate=0.1))   # 1100

Add argument and return type hints to calc_tax, the e-commerce-style tax calculator.

① On def calc_tax(price, rate):, add the hints price: int and rate: float.

② Add -> int before the : to declare the return type.

③ Keep the body as return int(price * (1 + rate)).

④ Verify with print(calc_tax(price=1000, rate=0.1)) (expect 1100).

(If your code runs correctly, the explanation will appear.)

Python Editor

Run code to see output

Variable Annotations — When to Write Them and When Not To

Type hints work on variables too — same shape: name: type = value. But variable annotations get noisy fast if you overuse them, and noise reduces readability.

# ❌ noisy — the value already tells you the type
name: str = "Alice"
age:  int = 30

# ✅ worth annotating — empty container, deferred init, or unclear type
results: list = []                       # empty list, will append later
cache:   dict = {}                       # may want to detail value type later
user_id: int  = fetch_id_from_session()  # makes the call site's expected type explicit

When in doubt: "functions always, variables sparingly"

In real projects, always annotate function and method signatures, but annotate variables only when the type isn't obvious. age = 30 doesn't need : int — adding it is just noise.

Type Hints Are Not Enforced at Runtime

Type hints are just hints. Python does not check or enforce them at runtime. A function declared def f(x: int) happily accepts f("abc") — the function runs as usual. Only later, when a calculation deep inside the body fails on the type mismatch, do you see a TypeError or similar — and that's a runtime error from the calculation, not from the hint itself.

So why bother? Because before the function ever runs, editors and static analyzers can detect type mismatches. VS Code's Pylance, PyCharm, mypy, pyright — all of them read your hints and underline mistakes in red, catching bugs before execution.

Browser Python runners don't show IDE warnings

The console on this page only runs your code — it has no static type-checking facility like VS Code does. So writing or violating type hints never produces red underlines or pre-run warnings here. The real benefit of type hints — the safety net of editor warnings before you run — only shows up locally with VS Code, PyCharm, etc.

Add type hints to both a User class and a describe_user function that consumes it.

① Define class User: with __init__(self, name: str, age: int) assigning self.name = name and self.age = age (annotate constructor arguments too).

② Add an instance method greet(self) -> str returning f"Hello, {self.name}" (don't forget the return annotation).

③ Outside the class, define describe_user(user: User) -> str that takes a User instance and returns f"{user.name} ({user.age})".

④ Create user = User("Alice", 30) and verify with print(user.greet()) and print(describe_user(user)).

Python Editor

Run code to see output

Three Places Type Hints Pay Off

Three benefits of type hints
readability upintent showsIDE warningsas you typestatic analysisin CI
Beyond readability, you get real-time IDE warnings and pre-run static checks (mypy / pyright) — concrete returns on the cost of writing hints.
QUIZ

Knowledge Check

Answer each question one by one.

Q1Which is the correct way to annotate def add(x, y): so both arguments and the return value are int?

Q2Which is the most accurate statement about Python type hints?

Q3Which is the most recommended place to add type hints?