PYTHON

Python Type Hints: Syntax, Usage, and Examples

Python type hints let you add type information to variables, function parameters, and return values. They make your code easier to read, easier to refactor, and friendlier to tools like linters and autocomplete.


How to Use Type Hints in Python

Type hints look like small labels next to values and function parts. Python ignores them at runtime, but editors and type checkers use them to catch mistakes early.

Basic syntax

Here’s the simplest place to start, a variable and a function:

name:str ="Mila"
age:int =28

defgreet(user: str) ->str:
returnf"Hi, {user}!"

  • name: str means name should be a string
  • age: int means age should be an integer
  • > str means greet() returns a string

Common built-in types

You’ll see these constantly:

is_active:bool =True
price:float =19.99
tags:list[str] = ["python","typing"]
settings:dict[str,str] = {"theme":"dark"}

Type hints for functions with multiple parameters

defcalculate_total(price: float, quantity:int, discount:float =0.0) ->float:
return price * quantity * (1 - discount)

Even without reading the function body, you already know what goes in and what comes out.

Optional values with None

A lot of bugs come from assuming a value exists when it doesn’t. Type hints make that situation obvious.

from typingimportOptional

deffind_user(username: str) ->Optional[dict[str,str]]:
if username =="admin":
return {"username":"admin","role":"owner"}
returnNone

That return type means: “You might get a dictionary, or you might get None.”

Union types in modern Python

If your version supports it (Python 3.10+), you can use | instead of Optional:

defparse_id(value: str) ->int |None:
if value.isdigit():
returnint(value)
returnNone


When to Use Type Hints

Type hints help in small scripts, but they really shine when the code grows and other people touch it.

1) When functions start getting reused across your project

Functions tend to live longer than you expect. Your “quick helper” becomes a key piece of logic.

Type hints make the function contract clear, which means fewer “wait, what does this accept?” moments.

2) When you work with data that changes shape

APIs, JSON responses, database rows, user input, config files, all of these can surprise you.

Adding type hints encourages you to handle edge cases early, instead of discovering them in production.

3) When you want better autocomplete and fewer silly bugs

Your editor can suggest the right methods on the right objects. That alone can save a ton of time.

Also, type checkers can catch mistakes like passing a string into a function that expects an int.

4) When you refactor code without breaking everything

Refactoring without types can feel like walking through a dark room barefoot. Something sharp is waiting.

With type hints, you get warnings when changes don’t match what the code expects.


Examples of Type Hints in Python

Let’s build up from everyday code to slightly more structured examples.

Example 1: Typing a function that processes user input

defnormalize_username(raw: str) ->str:
return raw.strip().lower()

username = normalize_username("  Srdjan  ")
print(username)# srdjan

A type hint here is simple, but it adds clarity for anyone reading the code.


Example 2: Typing lists and dictionaries

Say you’re storing course progress:

progress:dict[str,int] = {
"Intro to Python":80,
"Functions":55,
"Loops":100
}

completed_lessons:list[str] = ["variables","if statements","for loops"]

Now you can tell at a glance what the collection contains.


Example 3: Typing a function that may return None

This happens constantly with search logic.

from typingimportOptional

defget_discount(code: str) ->Optional[float]:
    discounts = {"SAVE10":0.10,"SAVE25":0.25}
return discounts.get(code)

discount = get_discount("SAVE10")
if discountisnotNone:
print(f"Discount: {discount * 100}%")
else:
print("Invalid code")

Type hints push you to handle the None case properly.


Example 4: Typing a class with attributes

Type hints also work inside classes and make your objects easier to understand.

classStudent:
def__init__(self, name: str, score:float) ->None:
self.name:str = name
self.score:float = score

defpassed(self) ->bool:
returnself.score >=60

student = Student("Amara",72.5)
print(student.passed())# True

Even beginners can read this and immediately know what’s going on.


Example 5: Typing a function that returns a tuple

Sometimes returning multiple values is useful, and you can describe that too.

defsplit_full_name(full_name: str) ->tuple[str,str]:
    parts = full_name.split(" ",1)
return parts[0], parts[1]

first, last = split_full_name("Mina Jovanovic")
print(first)# Mina
print(last)# Jovanovic

A hint like tuple[str, str] tells you the shape of the return value.


Learn More About Type Hints

Type hints go way beyond int and str. Once you know the basics, you can start typing more complicated patterns without making your code look like a math textbook.

Using typing imports like List, Dict, and Optional

Older versions of Python used List[str] and Dict[str, int] instead of list[str] and dict[str, int].

You’ll still see this in real codebases, especially older ones.

from typingimportList,Dict

names:List[str] = ["Luka","Noor","Sofia"]
scores:Dict[str,int] = {"Luka":95,"Noor":88}

Both styles work, the modern built-in style is just cleaner.


The Any type

Any means “skip type checking here.” It’s useful when you truly don’t know what’s coming in, like raw JSON.

from typingimportAny

data:Any = {"id":123,"items": ["a","b","c"]}

Try not to use Any everywhere, though. Too much of it defeats the point of adding hints.


Callable for functions you pass around

Functions are values in Python, so you can type them too.

from typingimportCallable

defapply_twice(func: Callable[[int],int], value:int) ->int:
return func(func(value))

defdouble(n: int) ->int:
return n *2

print(apply_twice(double,5))# 20

That Callable[[int], int] means: “a function that takes an int and returns an int.”


Type aliases for readability

Some types get long. Aliases keep things clean.

from typingimportOptional

UserId =int
UserName =str

defget_user_name(user_id: UserId) ->Optional[UserName]:
    users = {1:"Mira",2:"Kenan"}
return users.get(user_id)

Now the code reads almost like plain English.


Generics like list[T] and dict[K, V]

You’ve already seen generics without naming them. They’re just types with “slots.”

  • list[str] means list of strings
  • dict[str, int] means keys are strings, values are integers

This becomes extra useful when you build helper functions that handle many types.


Type hints do not enforce types at runtime

Python won’t stop you from doing this:

age:int ="twenty"

That line runs fine, because Python treats hints as metadata.

Type hints help through tooling, not runtime rules. If you want runtime enforcement, you’d need extra libraries or explicit checks.


Type hints and docstrings work well together

Type hints explain what a function expects, docstrings explain why it exists and how to use it.

defformat_price(amount: float, currency:str) ->str:
"""Format a number as a price string like '€19.99'."""
returnf"{currency}{amount:.2f}"

Hints describe the contract, the docstring adds context.


A quick “real-life” example: typing a small API response

This is the kind of code that benefits from hints fast:

from typingimport TypedDict

classCourse(TypedDict):
    title:str
    minutes:int
    completed:bool

course: Course = {"title":"Intro to CSS","minutes":45,"completed":False}

defcourse_summary(c: Course) ->str:
    status ="done"if c["completed"]else"in progress"
returnf"{c['title']} ({c['minutes']} min),{status}"

print(course_summary(course))

TypedDict helps when you use dictionary-shaped data that has a known structure.


Summary

Type hints add readable type information to Python code without changing how the program runs. They make functions and data structures easier to understand, help tools catch mistakes, and make refactoring less stressful as your project grows.