Database lookups, configuration values, and user input often produce "nothing found" results. Optional types make the possibility of None explicit in your code, forcing you to handle missing values before type checkers let you use the result.

Parsing Optional Values

optional_basics.py
# Optional basics

from typing import Optional

# Two equivalent ways
name1: Optional[str] = "Alice"
name2: str | None = 

print("name1:", name1)
print("name2:", name2)

# Function returning optional

def lookup_city(user_id: int) -> str | None:
    cities: dict[int, str] = {1: "Paris", 2: "Tokyo"}
    return cities.get(user_id)

print("lookup_city(1) =", lookup_city(1))
print("lookup_city(99) =", lookup_city(99))

# Optional basics

from typing import Optional

# Two equivalent ways
name1: Optional[str] = "Alice"
name2: str | None = 

print("name1:", name1)
print("name2:", name2)

# Function returning optional

def lookup_city(user_id: int) -> str | None:
    cities: dict[int, str] = {1: "Paris", 2: "Tokyo"}
    return cities.get(user_id)

print("lookup_city(1) =", lookup_city(1))
print("lookup_city(99) =", lookup_city(99))

# Optional basics

from typing import Optional

# Two equivalent ways
name1: Optional[str] = "Alice"
name2: str | None = 

print("name1:", name1)
print("name2:", name2)

# Function returning optional

def lookup_city(user_id: int) -> str | None:
    cities: dict[int, str] = {1: "Paris", 2: "Tokyo"}
    return cities.get(user_id)

print("lookup_city(1) =", lookup_city(1))
print("lookup_city(99) =", lookup_city(99))

narrowing.py
# Narrowing optionals

def greet(name: str | None) -> str:
    # Narrowing with is None
    if name is None:
        return "Hello, stranger"

    # Here name is treated as str by type checkers
    return f"Hello, {name.upper()}"

print(greet("Alice"))
print(greet(None))

# Guard function

def ensure_str(value: str | None) -> str:
    if value is None:
        raise ValueError("value is required")
    return value

print("ensure_str('x') =", ensure_str("x"))

optional_collections.py
# Optional with collections

from typing import Optional

# dict.get returns Optional[V]
ages: dict[str, int] = {"Alice": 30, "Bob": 27}

maybe_age: Optional[int] = ages.get("Charlie")
print("maybe_age:", maybe_age)

# Provide a default to avoid Optional
age_or_zero: int = ages.get("Charlie", 0)
print("age_or_zero:", age_or_zero)

# Optional element in list
scores: list[int | None] = [10, None, 30]

# Filter out None safely
clean: list[int] = [s for s in scores if s is not None]
print("clean:", clean)

sentinel.py
# Sentinel values

from typing import Any

# Sentinel object
MISSING = object()

# None could be a valid value, so we need a different “not provided” marker.

def get_setting(settings: dict[str, Any], key: str, default: Any = MISSING) -> Any:
    value = settings.get(key, MISSING)
    if value is not MISSING:
        return value

    if default is MISSING:
        raise KeyError(key)

    return default

cfg: dict[str, Any] = {"timeout": None, "retries": 3}

print("timeout (explicit None) =", get_setting(cfg, "timeout"))
print("retries =", get_setting(cfg, "retries"))
print("missing with default =", get_setting(cfg, "missing", 0))

parse_optional.py
# Parsing that can fail

from typing import Optional

# Optional parse

def try_parse_int(text: str) -> Optional[int]:
    try:
        return int(text)
    except ValueError:
        return None

inputs = ["10", "x", "42"]
parsed: list[int] = []

for t in inputs:
    value = try_parse_int(t)
    if value is None:
        print("skip:", t)
        continue
    parsed.append(value)

print("parsed:", parsed)

# Alternative: raise instead of Optional

def parse_int(text: str) -> int:
    return int(text)

print("parse_int('7') =", parse_int("7"))

Optional[T] - a type alias meaning `T | None`, indicating the value might be missing
narrowing - using `if x is not None:` checks to prove to the type checker that a value exists
sentinel - a unique object used when `None` is a valid value and you need to distinguish "not provided"

Exercise: practical.py

Build a config loader that handles missing keys gracefully