◀ My LearningMap
DAY 1 · LEVEL 1: PYTHON

Python type hints

45-60 minhands-onfree
Today you will be able to write a typed Python function, and explain what a type hint does and does not do.

1 · Warm-up 3 min

It is the first lesson, so the warm-up is a quick check of Day 0.

2 · Hook 2 min

What does Python do if a function wants a number and you give it the word “banana”?

Guess first, then open

It shrugs, and tries to run it. If something fails, it fails later, inside the function. That surprise is today’s lesson.

3 · The idea, in three small pieces 10 min

mypy, before you run

  • reads the hints
  • finds a mismatch
  • no code runs

Python, when it runs

  • ignores the hints
  • runs the line anyway
  • fails only if that line breaks
A hint is a sticky note for tools, not a lock for Python.

Piece 1: a hint is a sticky note

A type hint says what a function takes and returns, like a sticky note on a door: “numbers only, please”.

def double(x: int) -> int:
    return x * 2

Read it as: x should be an int, and the function gives back an int.

Piece 2: who reads the sticky note?

Reads the hint?
Python, while the program runsNo. It ignores it.
Your editorYes. It underlines mistakes.
mypy (a checker tool)Yes. It finds mistakes before you run.

Piece 3: checking while the program runs is a different job

If you want something to refuse bad data at runtime, you need validation. On Day 3 you will meet Pydantic, the bouncer who checks IDs at the door.

If you know another typed language (C# as the example): list[str] feels like List<string> and str | None feels like string?. The big difference: there is no compiler that stops the program. In Python, the checker is a friend who whispers, “are you sure?”.
Capstone: every function in the AI project we build is typed like this.

4 · Worked example 5 min

Read it first. Then run it.

def summarize(text: str, max_words: int = 50) -> str:
    words = text.split()
    return " ".join(words[:max_words])

print(summarize("hello brave new world", 2))
You should see
hello brave

5 · Try it 25-30 min

Four steps, from guided to free. Make a folder for your work and a file called practice_types.py. (This is the checklist you tick on My Learning.)

Step 1 Copy and run

Paste this into practice_types.py and run it with uv run python practice_types.py (or python3 practice_types.py).

from collections import Counter
import re


def word_count(text: str) -> int:
    return len(text.split())


def top_n_words(text: str, n: int = 5) -> list[str]:
    words = re.findall(r"\b\w+\b", text.lower())
    return [word for word, _ in Counter(words).most_common(n)]


def find_user(users: dict[str, str], user_id: str) -> str | None:
    return users.get(user_id)


if __name__ == "__main__":
    text = "the cat and the dog and the bird"
    print(word_count(text))
    print(top_n_words(text, 2))
    users = {"1": "Ann", "2": "Bob"}
    print(find_user(users, "1"))
    print(find_user(users, "9"))
You should see
8
['the', 'and']
Ann
None

Step 2 Change one thing

Change top_n_words(text, 2) to top_n_words(text, 3). Predict the output before you run it, then check.

Check your prediction
You should see
['the', 'and', 'cat']

The words “cat”, “dog” and “bird” each appear once, and ties keep the order they first appeared in.

Step 3 Break it on purpose

Make a file broken.py next to it:

from practice_types import word_count, find_user

users = {"1": "Ann", "2": "Bob"}
print(find_user(users, 1))
print(word_count(123))

Run it: uv run python broken.py.

You should see
None
AttributeError: 'int' object has no attribute 'split'

Notice: find_user quietly returned None for the wrong type, and word_count failed inside the function, not at the hint.

Now ask mypy. Install it with uv add --dev mypy (or pip install mypy), then run uv run mypy broken.py practice_types.py.

You should see (line numbers may match yours)
broken.py:4: error: Argument 2 to "find_user" has incompatible type "int"; expected "str" [arg-type]
broken.py:5: error: Argument 1 to "word_count" has incompatible type "int"; expected "str" [arg-type]

That is what hints are for: the checker finds both mistakes before you run anything.

Step 4 Your turn

Write a function shout(text: str, times: int = 1) -> str that returns the text in upper case, repeated times times, separated by spaces. Test it with shout("hello", 2). Then call shout("hello", "two") in a second file and let mypy catch it.

Hint 1

Turning text into upper case: text.upper().

Hint 2

A list can be repeated: ["a"] * 3 gives ["a", "a", "a"]. Then " ".join(...) glues the pieces with spaces.

Solution
def shout(text: str, times: int = 1) -> str:
    return " ".join([text.upper()] * times)


print(shout("hello", 2))
You should see
HELLO HELLO

And mypy on the bad call says: Argument 2 to "shout" has incompatible type "str"; expected "int".

6 · Stuck? anytime

7 · Check yourself 5 min

  1. What does a type hint guarantee while the program runs?
  2. In Step 3, why did find_user(users, 1) print None instead of failing?
  3. Which tool would have caught the mistake before you ran the program?
Answers
  1. Nothing. Python ignores hints at runtime; they are for you, your editor and tools like mypy.
  2. Because dict.get returns None when the key is missing, and the integer 1 is not the key "1". Python never checked that the key was supposed to be a string.
  3. mypy (or an editor that reads type hints).

+ · Game: Sticky Note Sorter 5 min

Fetch the pixel dog delivers a small function with a sticky note (a type hint) and one call. Two questions each round: what does Python do when it runs? and does mypy flag it before you run? 8 rounds, 2 points each. Real results, no tricks.

Ready? You need no setup. Just read the code and decide.

Tick “Play the Sticky Note Sorter game” on My Learning when you reach the end. Any score counts. If you score under 10, read “Piece 2” again and replay: the rounds are shuffled.

8 · Your win, and what’s next 3 min

You made: typed functions, saw Python ignore a wrong type, and made mypy catch it. That is a real skill.