[{"data":1,"prerenderedAt":827},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-type-hints-explained":3},{"id":4,"title":5,"body":6,"description":814,"difficulty":815,"extension":816,"framework":817,"frameworkSlug":43,"meta":818,"navigation":98,"order":52,"path":819,"qaPath":820,"seo":821,"stem":822,"subtopic":823,"topic":824,"topicSlug":488,"updated":825,"__hash__":826},"blog\u002Fblog\u002Fpython-type-hints-explained.md","Python Type Hints Explained — Annotations, Optional, and mypy",{"type":7,"value":8,"toc":803},"minimark",[9,14,23,27,38,122,138,142,153,242,249,253,286,437,452,462,490,549,559,563,584,646,661,665,675,729,746,752,756,799],[10,11,13],"h2",{"id":12},"python-type-hints-explained","Python type hints, explained",[15,16,17,18,22],"p",{},"Type hints let you annotate what types your functions and variables expect — improving\neditor autocomplete, catching bugs before runtime, and documenting intent. The catch that\ninterviewers love: Python ",[19,20,21],"strong",{},"does not enforce them at runtime",". Understanding what hints do\nand don't do is the heart of this topic. This guide covers the syntax and the tooling.",[10,24,26],{"id":25},"hints-are-not-enforced-at-runtime","Hints are not enforced at runtime",[15,28,29,30,33,34,37],{},"This is the single most important fact: type hints are ",[19,31,32],{},"annotations",", not runtime checks.\nThe interpreter records them but does ",[19,35,36],{},"not"," validate them — passing the \"wrong\" type runs\nfine unless your own code breaks.",[39,40,45],"pre",{"className":41,"code":42,"language":43,"meta":44,"style":44},"language-python shiki shiki-themes github-light github-dark","def greet(name: str) -> str:\n    return \"hi \" + name\n\ngreet(42)        # TypeError — but from the +, NOT from the hint\n                 # Python never checked that 42 is a str\n","python","",[46,47,48,77,93,100,116],"code",{"__ignoreMap":44},[49,50,53,57,61,65,69,72,74],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"szBVR","def",[49,58,60],{"class":59},"sScJk"," greet",[49,62,64],{"class":63},"sVt8B","(name: ",[49,66,68],{"class":67},"sj4cs","str",[49,70,71],{"class":63},") -> ",[49,73,68],{"class":67},[49,75,76],{"class":63},":\n",[49,78,80,83,87,90],{"class":51,"line":79},2,[49,81,82],{"class":55},"    return",[49,84,86],{"class":85},"sZZnC"," \"hi \"",[49,88,89],{"class":55}," +",[49,91,92],{"class":63}," name\n",[49,94,96],{"class":51,"line":95},3,[49,97,99],{"emptyLinePlaceholder":98},true,"\n",[49,101,103,106,109,112],{"class":51,"line":102},4,[49,104,105],{"class":63},"greet(",[49,107,108],{"class":67},"42",[49,110,111],{"class":63},")        ",[49,113,115],{"class":114},"sJ8bj","# TypeError — but from the +, NOT from the hint\n",[49,117,119],{"class":51,"line":118},5,[49,120,121],{"class":114},"                 # Python never checked that 42 is a str\n",[15,123,124,125,128,129,132,133,137],{},"Hints exist for ",[19,126,127],{},"humans and tools",": editors use them for autocomplete and warnings, and\n",[19,130,131],{},"static checkers"," like mypy verify them ",[134,135,136],"em",{},"before"," you run. Enforcement is opt-in via those\nexternal tools (or libraries like Pydantic), never automatic.",[10,139,141],{"id":140},"basic-annotations","Basic annotations",[15,143,144,145,148,149,152],{},"Annotate parameters, return types, and variables with ",[46,146,147],{},"name: Type",". The return type goes\nafter ",[46,150,151],{},"->",".",[39,154,156],{"className":41,"code":155,"language":43,"meta":44,"style":44},"def add(a: int, b: int) -> int:\n    return a + b\n\ncount: int = 0\nnames: list[str] = []\nratio: float = 1.5\n",[46,157,158,182,195,199,212,228],{"__ignoreMap":44},[49,159,160,162,165,168,171,174,176,178,180],{"class":51,"line":52},[49,161,56],{"class":55},[49,163,164],{"class":59}," add",[49,166,167],{"class":63},"(a: ",[49,169,170],{"class":67},"int",[49,172,173],{"class":63},", b: ",[49,175,170],{"class":67},[49,177,71],{"class":63},[49,179,170],{"class":67},[49,181,76],{"class":63},[49,183,184,186,189,192],{"class":51,"line":79},[49,185,82],{"class":55},[49,187,188],{"class":63}," a ",[49,190,191],{"class":55},"+",[49,193,194],{"class":63}," b\n",[49,196,197],{"class":51,"line":95},[49,198,99],{"emptyLinePlaceholder":98},[49,200,201,204,206,209],{"class":51,"line":102},[49,202,203],{"class":63},"count: ",[49,205,170],{"class":67},[49,207,208],{"class":55}," =",[49,210,211],{"class":67}," 0\n",[49,213,214,217,219,222,225],{"class":51,"line":118},[49,215,216],{"class":63},"names: list[",[49,218,68],{"class":67},[49,220,221],{"class":63},"] ",[49,223,224],{"class":55},"=",[49,226,227],{"class":63}," []\n",[49,229,231,234,237,239],{"class":51,"line":230},6,[49,232,233],{"class":63},"ratio: ",[49,235,236],{"class":67},"float",[49,238,208],{"class":55},[49,240,241],{"class":67}," 1.5\n",[15,243,244,245,248],{},"These are visible at runtime in ",[46,246,247],{},"__annotations__",", which is how tools like dataclasses and\nPydantic read them — but again, nothing is checked automatically.",[10,250,252],{"id":251},"optional-and-union-and-the-syntax","Optional and Union (and the | syntax)",[15,254,255,258,259,262,263,266,267,269,270,273,274,277,278,281,282,285],{},[46,256,257],{},"Optional[X]"," means \"",[46,260,261],{},"X"," or ",[46,264,265],{},"None","\" — common for default-",[46,268,265],{}," arguments. ",[46,271,272],{},"Union[X, Y]","\nmeans \"either type.\" Since ",[19,275,276],{},"Python 3.10"," you can write ",[46,279,280],{},"X | None"," and ",[46,283,284],{},"X | Y"," directly.",[39,287,289],{"className":41,"code":288,"language":43,"meta":44,"style":44},"from typing import Optional, Union\n\ndef find(id: int) -> Optional[str]:      # may return a str or None\n    ...\n\ndef parse(x: Union[int, str]) -> int:    # accepts int or str\n    ...\n\n# 3.10+ shorthand:\ndef find(id: int) -> str | None: ...\ndef parse(x: int | str) -> int: ...\n",[46,290,291,305,309,332,337,341,369,374,379,385,412],{"__ignoreMap":44},[49,292,293,296,299,302],{"class":51,"line":52},[49,294,295],{"class":55},"from",[49,297,298],{"class":63}," typing ",[49,300,301],{"class":55},"import",[49,303,304],{"class":63}," Optional, Union\n",[49,306,307],{"class":51,"line":79},[49,308,99],{"emptyLinePlaceholder":98},[49,310,311,313,316,319,321,324,326,329],{"class":51,"line":95},[49,312,56],{"class":55},[49,314,315],{"class":59}," find",[49,317,318],{"class":63},"(id: ",[49,320,170],{"class":67},[49,322,323],{"class":63},") -> Optional[",[49,325,68],{"class":67},[49,327,328],{"class":63},"]:      ",[49,330,331],{"class":114},"# may return a str or None\n",[49,333,334],{"class":51,"line":102},[49,335,336],{"class":67},"    ...\n",[49,338,339],{"class":51,"line":118},[49,340,99],{"emptyLinePlaceholder":98},[49,342,343,345,348,351,353,356,358,361,363,366],{"class":51,"line":230},[49,344,56],{"class":55},[49,346,347],{"class":59}," parse",[49,349,350],{"class":63},"(x: Union[",[49,352,170],{"class":67},[49,354,355],{"class":63},", ",[49,357,68],{"class":67},[49,359,360],{"class":63},"]) -> ",[49,362,170],{"class":67},[49,364,365],{"class":63},":    ",[49,367,368],{"class":114},"# accepts int or str\n",[49,370,372],{"class":51,"line":371},7,[49,373,336],{"class":67},[49,375,377],{"class":51,"line":376},8,[49,378,99],{"emptyLinePlaceholder":98},[49,380,382],{"class":51,"line":381},9,[49,383,384],{"class":114},"# 3.10+ shorthand:\n",[49,386,388,390,392,394,396,398,400,403,406,409],{"class":51,"line":387},10,[49,389,56],{"class":55},[49,391,315],{"class":59},[49,393,318],{"class":63},[49,395,170],{"class":67},[49,397,71],{"class":63},[49,399,68],{"class":67},[49,401,402],{"class":55}," |",[49,404,405],{"class":67}," None",[49,407,408],{"class":63},": ",[49,410,411],{"class":67},"...\n",[49,413,415,417,419,422,424,426,429,431,433,435],{"class":51,"line":414},11,[49,416,56],{"class":55},[49,418,347],{"class":59},[49,420,421],{"class":63},"(x: ",[49,423,170],{"class":67},[49,425,402],{"class":55},[49,427,428],{"class":67}," str",[49,430,71],{"class":63},[49,432,170],{"class":67},[49,434,408],{"class":63},[49,436,411],{"class":67},[15,438,439,441,442,445,446,448,449,451],{},[46,440,257],{}," is exactly ",[46,443,444],{},"Union[X, None]"," — it does ",[19,447,36],{}," mean \"optional argument,\" just\n\"could be None.\" A checker will then force you to handle the ",[46,450,265],{}," case before using the\nvalue.",[10,453,455,456,458,459],{"id":454},"built-in-generics-listint-dictstr-int","Built-in generics: list",[49,457,170],{},", dict",[49,460,461],{},"str, int",[15,463,464,465,468,469,355,472,355,475,478,479,355,482,485,486,489],{},"Since ",[19,466,467],{},"Python 3.9"," you annotate containers with the built-in types directly —\n",[46,470,471],{},"list[int]",[46,473,474],{},"dict[str, int]",[46,476,477],{},"tuple[int, ...]"," — instead of importing ",[46,480,481],{},"List",[46,483,484],{},"Dict"," from\n",[46,487,488],{},"typing"," (which are now deprecated aliases).",[39,491,493],{"className":41,"code":492,"language":43,"meta":44,"style":44},"def totals(scores: dict[str, list[int]]) -> dict[str, int]:\n    return {name: sum(vals) for name, vals in scores.items()}\n",[46,494,495,524],{"__ignoreMap":44},[49,496,497,499,502,505,507,510,512,515,517,519,521],{"class":51,"line":52},[49,498,56],{"class":55},[49,500,501],{"class":59}," totals",[49,503,504],{"class":63},"(scores: dict[",[49,506,68],{"class":67},[49,508,509],{"class":63},", list[",[49,511,170],{"class":67},[49,513,514],{"class":63},"]]) -> dict[",[49,516,68],{"class":67},[49,518,355],{"class":63},[49,520,170],{"class":67},[49,522,523],{"class":63},"]:\n",[49,525,526,528,531,534,537,540,543,546],{"class":51,"line":79},[49,527,82],{"class":55},[49,529,530],{"class":63}," {name: ",[49,532,533],{"class":67},"sum",[49,535,536],{"class":63},"(vals) ",[49,538,539],{"class":55},"for",[49,541,542],{"class":63}," name, vals ",[49,544,545],{"class":55},"in",[49,547,548],{"class":63}," scores.items()}\n",[15,550,551,552,555,556,558],{},"The lowercase built-in forms are the modern style; the capitalised ",[46,553,554],{},"typing.List","\u002F",[46,557,484],{}," are\nonly needed on Python 3.8 and earlier.",[10,560,562],{"id":561},"any-vs-object","Any vs object",[15,564,565,568,569,572,573,576,577,580,581,583],{},[46,566,567],{},"Any"," is an ",[19,570,571],{},"escape hatch",": it's compatible with everything in both directions, so the\nchecker stops checking — use it sparingly. ",[46,574,575],{},"object"," is the actual base of all types, so you\ncan ",[134,578,579],{},"assign"," anything to it but can only do ",[46,582,575],{},"-level operations without a cast.",[39,585,587],{"className":41,"code":586,"language":43,"meta":44,"style":44},"from typing import Any\n\nx: Any = \"anything\"\nx.foo().bar()        # checker says nothing — Any disables checking\n\ny: object = \"hi\"\ny.upper()            # checker ERROR — object has no .upper(); narrow it first\n",[46,588,589,600,604,614,622,626,638],{"__ignoreMap":44},[49,590,591,593,595,597],{"class":51,"line":52},[49,592,295],{"class":55},[49,594,298],{"class":63},[49,596,301],{"class":55},[49,598,599],{"class":63}," Any\n",[49,601,602],{"class":51,"line":79},[49,603,99],{"emptyLinePlaceholder":98},[49,605,606,609,611],{"class":51,"line":95},[49,607,608],{"class":63},"x: Any ",[49,610,224],{"class":55},[49,612,613],{"class":85}," \"anything\"\n",[49,615,616,619],{"class":51,"line":102},[49,617,618],{"class":63},"x.foo().bar()        ",[49,620,621],{"class":114},"# checker says nothing — Any disables checking\n",[49,623,624],{"class":51,"line":118},[49,625,99],{"emptyLinePlaceholder":98},[49,627,628,631,633,635],{"class":51,"line":230},[49,629,630],{"class":63},"y: ",[49,632,575],{"class":67},[49,634,208],{"class":55},[49,636,637],{"class":85}," \"hi\"\n",[49,639,640,643],{"class":51,"line":371},[49,641,642],{"class":63},"y.upper()            ",[49,644,645],{"class":114},"# checker ERROR — object has no .upper(); narrow it first\n",[15,647,648,649,651,652,654,655,657,658,660],{},"Rule of thumb: ",[46,650,567],{}," means \"trust me, stop checking\"; ",[46,653,575],{}," means \"literally any object,\nbut stay type-safe.\" Prefer ",[46,656,575],{}," (or a precise type) and reserve ",[46,659,567],{}," for genuinely\ndynamic boundaries.",[10,662,664],{"id":663},"what-mypy-does","What mypy does",[15,666,667,670,671,674],{},[46,668,669],{},"mypy"," (and similar checkers like Pyright) is a ",[19,672,673],{},"static analysis tool",": it reads your\nhints and flags type mismatches without running the code. This is where hints turn into\nactual bug-catching.",[39,676,678],{"className":41,"code":677,"language":43,"meta":44,"style":44},"def double(n: int) -> int:\n    return n * 2\n\ndouble(\"oops\")       # mypy: error: Argument 1 to \"double\" has incompatible type \"str\"\n",[46,679,680,698,711,715],{"__ignoreMap":44},[49,681,682,684,687,690,692,694,696],{"class":51,"line":52},[49,683,56],{"class":55},[49,685,686],{"class":59}," double",[49,688,689],{"class":63},"(n: ",[49,691,170],{"class":67},[49,693,71],{"class":63},[49,695,170],{"class":67},[49,697,76],{"class":63},[49,699,700,702,705,708],{"class":51,"line":79},[49,701,82],{"class":55},[49,703,704],{"class":63}," n ",[49,706,707],{"class":55},"*",[49,709,710],{"class":67}," 2\n",[49,712,713],{"class":51,"line":95},[49,714,99],{"emptyLinePlaceholder":98},[49,716,717,720,723,726],{"class":51,"line":102},[49,718,719],{"class":63},"double(",[49,721,722],{"class":85},"\"oops\"",[49,724,725],{"class":63},")       ",[49,727,728],{"class":114},"# mypy: error: Argument 1 to \"double\" has incompatible type \"str\"\n",[39,730,734],{"className":731,"code":732,"language":733,"meta":44,"style":44},"language-bash shiki shiki-themes github-light github-dark","mypy yourmodule.py   # run the checker; it never executes your program\n","bash",[46,735,736],{"__ignoreMap":44},[49,737,738,740,743],{"class":51,"line":52},[49,739,669],{"class":59},[49,741,742],{"class":85}," yourmodule.py",[49,744,745],{"class":114},"   # run the checker; it never executes your program\n",[15,747,748,749,751],{},"Add it to CI and you catch a whole class of ",[46,750,265],{},"-handling and wrong-type bugs before they\nreach production — the real payoff of annotating your code.",[10,753,755],{"id":754},"recap","Recap",[15,757,758,759,762,763,766,767,281,769,772,773,555,775,777,778,555,781,783,784,786,787,789,790,792,793,795,796,798],{},"Type hints are ",[19,760,761],{},"annotations, not runtime checks"," — Python records but never enforces\nthem; the value comes from editors and ",[19,764,765],{},"static checkers like mypy",". Annotate with\n",[46,768,147],{},[46,770,771],{},"-> ReturnType",", use ",[46,774,257],{},[46,776,280],{}," for \"maybe None\" and\n",[46,779,780],{},"Union",[46,782,284],{}," for alternatives, and prefer built-in generics (",[46,785,471],{},",\n",[46,788,474],{},") on modern Python. Treat ",[46,791,567],{}," as an escape hatch that disables checking\nand ",[46,794,575],{}," as the safe \"anything\" type. Hints don't change how your program runs — they\nmake bugs visible ",[134,797,136],{}," it runs.",[800,801,802],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":44,"searchDepth":79,"depth":79,"links":804},[805,806,807,808,809,811,812,813],{"id":12,"depth":79,"text":13},{"id":25,"depth":79,"text":26},{"id":140,"depth":79,"text":141},{"id":251,"depth":79,"text":252},{"id":454,"depth":79,"text":810},"Built-in generics: listint, dictstr, int",{"id":561,"depth":79,"text":562},{"id":663,"depth":79,"text":664},{"id":754,"depth":79,"text":755},"How Python type hints work — whether they're enforced at runtime, Optional and Union (and the | syntax), built-in generics like list[int], Any vs object, and what mypy does.","medium","md","Python",{},"\u002Fblog\u002Fpython-type-hints-explained","\u002Fpython\u002Ftyping\u002Ftype-hints",{"title":5,"description":814},"blog\u002Fpython-type-hints-explained","Type Hints & Annotations","Type Hints & Typing","2026-06-19","7dz868Sh-wjBjA4fV5AeBq4hxVOhFG0DwMb7oE7mSvA",1781808673081]