Type Hints in Python: Functions, Collections and Checked Examples

Learn what Python type hints do, how to annotate common data shapes, and how a checker catches mistakes through exact, runnable examples.

KnowledgeGate Team

Exam prep & CS education

Updated 5 Nov 20266 min read
Type Hints in Python: Functions, Collections and Checked Examples

A Python function may accept the wrong kind of value, run several lines, and fail far from the call that caused the mistake. Type hints make the intended contract visible to the reader, editor, and static checker, but they are not runtime validation. Python type hints make variables and function signatures explicit, describe containers, unions, and structured records, and support a checker workflow. The examples use [72, 85, 91, 68] with bonus 2, total 324, and average 81.0.

Type hints in Python describe a contract, not a runtime gate

A type hint states the intended type of a variable, parameter, or return value.

python
def double(value: int) -> int:
    return value * 2

print(double(21))
print(double("KG"))

The outputs are 42 and KGKG. The second call violates the annotation, yet Python performs string repetition. An annotation alone neither converts nor validates data.

The interpreter runs the program, an editor displays hints, and a static checker such as mypy analyses calls without running them. It can flag double("KG") because str was supplied where int was declared. Frameworks may inspect annotations; annotations alone are not runtime gates.

Python type hint syntax for variables, parameters and return values

python
student_name: str = "Asha"
attempts: int = 3
accuracy: float = 82.5
is_ready: bool = True

def adjusted_score(score: int, bonus: int = 2) -> int:
    return score + bonus

print(adjusted_score(78))

The colon attaches a type to a name. Parameters are annotated separately, 2 is the bonus default, and -> int declares the return type. The call prints 80.

In def show_result(name: str, score: int) -> None:, -> None means no useful value should return. A missing annotation leaves a gap; explicit Any deliberately permits arbitrary operations. They are different choices.

For call semantics, defaults, return values and scope, see Functions in Python: Parameters, Return Values, Scope and Examples. Type annotations add a separate static contract at those function boundaries.

Built-in list[int] requires Python 3.9 or later; int | None requires Python 3.10 or later. Older code can import List[int] and Optional[int] from typing.

Type hints for Python lists, dictionaries and tuples

Value shape

Annotation

Exact example

Integer list

list[int]

[72, 85, 91, 68]

Named integer values

dict[str, int]

{"Python": 84, "SQL": 76, "OS": 90}

Unique strings

set[str]

{"python", "sql"}

Fixed pair

tuple[int, float]

(324, 81.0)

Variable-length integer tuple

tuple[int, ...]

(72, 85, 91, 68)

For the dictionary, 84 + 76 + 90 = 250, then 250 / 3 = 83.333..., displayed as 83.33. If repeated, name the unchanged shape ScoreMap = dict[str, int].

Read nested types left to right: attempts: dict[str, list[int]] = {"Asha": [72, 81, 88], "Ravi": [65, 79]} maps str names to inner lists of int values.

Fully worked Python type hint example: summarise four scores

Introduction to Python: Core Concepts, Worked Trace and Exam Patterns traces loops and runtime state over score data. In contrast, summarise_scores uses score data to expose a static contract: list elements, a defaulted parameter and a two-value return.

python
def summarise_scores(
    scores: list[int], bonus: int = 0
) -> tuple[int, float]:
    if not scores:
        raise ValueError("scores must not be empty")

    adjusted: list[int] = [score + bonus for score in scores]
    total: int = sum(adjusted)
    average: float = total / len(adjusted)
    return total, average

total, average = summarise_scores([72, 85, 91, 68], bonus=2)
print(total)
print(f"{average:.1f}")

Add 2 to each score to get [74, 87, 93, 70]. The total is 74 + 87 + 93 + 70 = 324; there are 4 scores; and 324 / 4 = 81.0. The returned tuple[int, float] is (324, 81.0), so the printed lines are exactly 324 and 81.0.

summarise_scores([72, "85", 91, 68], bonus=2) violates list[int], so a checker should reject the string item. Unchecked execution fails when Python adds integer 2 to string "85". The ValueError guard handles an empty list, not a wrong element type.

Typed flow for summarise_scores: scores [72, 85, 91, 68] with bonus 2 produce (324, 81.0), with a red path for a string item.

Optional values, unions and type narrowing in Python

python
def percentage(score: int, maximum: int | None = None) -> float:
    if maximum is None:
        maximum = 100
    if maximum <= 0:
        raise ValueError("maximum must be positive")
    return round(score / maximum * 100, 2)

print(percentage(42, 50))
print(percentage(42))

The outputs are 84.0 from 42 / 50 * 100 = 84.0, and 42.0 from 42 / 100 * 100 = 42.0. Before the check, maximum may be int or None; after the branch assigns 100, the arithmetic path has an int.

The union int | None permits None; = None makes the argument omissible. Use int | str only when both forms belong to the contract, not to silence errors. Any suppresses most useful checking.

Decision tree for percentage: maximum None becomes 100 giving 42.0, maximum 50 gives 84.0, and maximum 0 raises ValueError.

TypedDict and Callable for structured Python data

python
from typing import TypedDict

class Student(TypedDict):
    name: str
    scores: list[int]

meera: Student = {"name": "Meera", "scores": [84, 90, 78]}

def mean_score(student: Student) -> float:
    return sum(student["scores"]) / len(student["scores"])

print(mean_score(meera))

Here 84 + 90 + 78 = 252, and 252 / 3 = 84.0, so the output is 84.0. A checker can catch a missing name or a string inside scores, but the runtime value remains an ordinary dictionary without separate validation.

python
from typing import Callable

def add_five(score: int) -> int:
    return score + 5

def apply_rule(score: int, rule: Callable[[int], int]) -> int:
    return rule(score)

apply_rule(78, add_five) returns 83. Use built-in containers for homogeneous data, TypedDict for named dictionary fields, and Callable for a passed function. Classes, protocols, and generic type parameters are later topics.

Python type hint mistakes and how coding questions test them

Mistake

What goes wrong

Repair

Parameter items: list[int] = []

One mutable default is shared across calls

Use items: list[int] | None = None, then create a list inside

Optional[str] without = None

The argument is still required

Add = None only when omission is intended

cast(int, value)

It changes only the checker's view

Convert or validate the runtime value separately

Broad Any

Useful checking disappears

Model the actual accepted types

Repeated # type: ignore

Real mismatches are hidden

Repair the model or input

Save the worked function in scores.py, add the bad string call, and run the project's checker, such as mypy scores.py. Expect a list-item or argument incompatibility without relying on version-specific wording. Restore 85, rerun the checker, then run Python and confirm 324 and 81.0. Static checking complements tests and runtime validation; it replaces neither.

Typical tasks ask you to choose str | None for a lookup that may miss, explain why Optional[str] creates no default, annotate a callback, find an Any leak, or predict unchecked double("KG") returns KGKG. Type hints improve clarity and defect detection, but not the algorithmic cost explained in Time Complexity and Asymptotic Notation: Big-O.

Type hints in Python: the short version and next exercise

  • Annotate public function boundaries first.

  • Keep container element types explicit.

  • Model missing values with a union and a deliberate default.

  • Narrow a union before using one of its members.

  • Use TypedDict for a stable dictionary shape.

  • Run a static checker alongside ordinary tests.

The central result is [72, 85, 91, 68] plus bonus 2 becoming (324, 81.0).

Now call summarise_scores([80, 75, 95], bonus=1). The adjusted scores are [81, 76, 96], the total is 81 + 76 + 96 = 253, and the average is 253 / 3 = 84.333..., which the same .1f format prints as 84.3. Add name: str beside this result in a TypedDict. Then replace 95 with "95", confirm that the checker catches it, and restore the integer.

For the complete language sequence and more practice, continue with Python Course: Concepts, MCQs & Coding. Use the Coding & DSA practice path for mixed language and algorithm practice.