Skip to main content

Python Type Hints: Add Types, Generics, and the typing Module

Intermediate25 min6 exercises95 XP
0/6 exercises

Imagine ordering food at a restaurant. The menu says "Spicy Level: 1-5" — that's a type hint. It doesn't stop you from writing "eleven" on the order form, but it tells everyone what's expected. If you follow it, things go smoothly. If you ignore it, your meal might come out wrong.

Type hints in Python work the same way. They're optional annotations that document what types your functions expect and return. Python won't enforce them at runtime, but tools like mypy can check them before your code runs — catching bugs before they become problems.

How Do You Add Type Hints to Variables and Functions?

The syntax is simple. For variables, add a colon and the type after the name. For functions, annotate each parameter and use -> for the return type.

Basic type annotations
Loading editor...

The annotations don't change how the code runs. greet(42) would still work at runtime — Python ignores type hints during execution. But a type checker would flag it as an error.

Add Type Hints to a Function
Write Code

Add proper type hints to this function and its variables. The function takes a list of integers and returns their average as a float. Also add type hints to the two variables.

Make the function work correctly, then print the result for the list [10, 20, 30, 40].

Loading editor...

What Are Optional and Union Types?

Sometimes a value can be one of several types. A function might return a string or None. A parameter might accept an int or a float. Python's typing module provides Optional and Union for these cases.

Optional and Union types
Loading editor...

Optional[str] is shorthand for Union[str, None] — it means "a string or None." Use Optional when a value might be absent, and Union when a value can be one of several distinct types.

How Do You Type Lists, Dicts, and Tuples?

Plain list, dict, and tuple annotations don't tell you what's inside the collection. The typing module lets you specify element types for precise annotations.

Typing collections with element types
Loading editor...
Type a Config Function
Write Code

Write a function get_config that takes a Dict[str, str] of config values and an Optional[str] default value. It should look up a key and return the value if found, or the default if not.

Type-hint everything: parameters, return type, and the config variable.

config = {'host': 'localhost', 'port': '8080'}
print(get_config(config, 'host'))       # localhost
print(get_config(config, 'debug', 'off'))  # off
print(get_config(config, 'missing'))    # None
Loading editor...

How Do You Simplify Complex Type Annotations?

When type annotations get long and repetitive, type aliases let you give them short, descriptive names. This keeps your code readable.

Without type alias — hard to read
def process(data: Dict[str, List[Tuple[int, float]]]) -> Dict[str, float]:
    ...
With type alias — clear and descriptive
Measurements = Dict[str, List[Tuple[int, float]]]
Summary = Dict[str, float]

def process(data: Measurements) -> Summary:
    ...
Type aliases for geometric types
Loading editor...
Predict the Output with Type Hints
Predict Output

What does this code print? Remember, type hints don't affect runtime behavior.

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

result = add('hello', ' world')
print(result)
print(type(result).__name__)

Write two print() statements with the exact output.

Loading editor...

How Do You Type Functions and Callbacks?

Since functions are objects in Python, you sometimes need to annotate a parameter that expects a function. The Callable type handles this.

Callable type for function parameters
Loading editor...

Callable[[int], int] means "a function that takes one int argument and returns an int." The first part is a list of parameter types, and the second is the return type.

Type a Higher-Order Function
Write Code

Write a function filter_by that takes a List[int] and a predicate function (Callable[[int], bool]) and returns a List[int] of items that pass the predicate.

Test it with an is_even function on [1, 2, 3, 4, 5, 6].

Loading editor...

What Is the Difference Between Runtime and Static Type Checking?

This is the most important thing to understand about Python type hints: they exist in two separate worlds.

Static checking happens before your code runs. Tools like mypy, pyright, and your IDE analyze your code and find type errors without executing anything. This catches bugs like passing a string where an int is expected.

Runtime checking happens while your code runs. Python itself does NOT check type hints at runtime. If you want runtime enforcement, you need to add it yourself using isinstance() checks or libraries like Pydantic.

No runtime enforcement (default)
def add(a: int, b: int) -> int:
    return a + b

# Works at runtime! No error.
print(add("hello", " world"))
Manual runtime checking
def add(a: int, b: int) -> int:
    if not isinstance(a, int) or not isinstance(b, int):
        raise TypeError("Both arguments must be int")
    return a + b

print(add(3, 4))  # Works
# add("hello", " world")  # TypeError!
Add Runtime Type Checking
Write Code

Write a function safe_divide(a: float, b: float) -> str that:

1. Checks that both arguments are int or float using isinstance. If not, return 'Error: arguments must be numbers'.

2. Checks if b is zero. If so, return 'Error: division by zero'.

3. Otherwise, returns the result as a string with 2 decimal places.

print(safe_divide(10, 3))      # 3.33
print(safe_divide(10, 0))      # Error: division by zero
print(safe_divide("10", 3))   # Error: arguments must be numbers
Loading editor...
Type a Data Processing Function
Write Code

Write a fully type-annotated function summarize_scores that takes a Dict[str, List[int]] mapping student names to their test scores, and returns a Dict[str, float] mapping each student to their average score.

scores = {
    'Alice': [90, 85, 92],
    'Bob': [78, 88, 84]
}
print(summarize_scores(scores))
# {'Alice': 89.0, 'Bob': 83.33}

Round averages to 2 decimal places.

Loading editor...

What Should You Remember About Type Hints?

Type hints make Python code clearer, more maintainable, and less bug-prone. They're one of the most impactful features added to modern Python.

Key takeaways:

  • Type hints are annotations, not enforcement — Python ignores them at runtime
  • Basic syntax: name: str = 'Alice' and def greet(name: str) -> str:
  • Optional[X] means X or None; Union[X, Y] means X or Y
  • List[int], Dict[str, int], Tuple[str, int] specify element types
  • Type aliases simplify complex annotations
  • Callable[[args], return] types function parameters
  • Static tools (mypy, pyright) check types before runtime
  • Use isinstance() or Pydantic for runtime enforcement