[{"data":1,"prerenderedAt":716},["ShallowReactive",2],{"blog-\u002Fblog\u002Fpython-closures-explained":3},{"id":4,"title":5,"body":6,"description":702,"difficulty":703,"extension":704,"framework":705,"frameworkSlug":39,"meta":706,"navigation":106,"order":75,"path":707,"qaPath":708,"seo":709,"stem":710,"subtopic":711,"topic":712,"topicSlug":713,"updated":714,"__hash__":715},"blog\u002Fblog\u002Fpython-closures-explained.md","Python Closures Explained — Free Variables, nonlocal, and the Loop Trap",{"type":7,"value":8,"toc":693},"minimark",[9,14,23,27,34,170,188,192,202,239,242,246,257,327,342,396,403,407,418,511,533,537,540,659,669,673,689],[10,11,13],"h2",{"id":12},"python-closures-explained","Python closures, explained",[15,16,17,18,22],"p",{},"A closure is a function that ",[19,20,21],"strong",{},"remembers"," variables from where it was defined, even after\nthat enclosing scope has returned. Closures are the foundation of decorators, callbacks, and\nmany functional patterns — and the late-binding loop trap is a perennial interview favourite.",[10,24,26],{"id":25},"what-a-closure-is","What a closure is",[15,28,29,30,33],{},"A closure happens when a nested function references a variable from its enclosing function\nand is then returned (or otherwise outlives that function). The variable it \"closes over\" is\ncalled a ",[19,31,32],{},"free variable",":",[35,36,41],"pre",{"className":37,"code":38,"language":39,"meta":40,"style":40},"language-python shiki shiki-themes github-light github-dark","def make_multiplier(factor):\n    def multiply(n):\n        return n * factor     # factor is a free variable\n    return multiply\n\ndouble = make_multiplier(2)\ntriple = make_multiplier(3)\ndouble(5)     # 10\ntriple(5)     # 15\n","python","",[42,43,44,61,73,92,101,108,127,142,157],"code",{"__ignoreMap":40},[45,46,49,53,57],"span",{"class":47,"line":48},"line",1,[45,50,52],{"class":51},"szBVR","def",[45,54,56],{"class":55},"sScJk"," make_multiplier",[45,58,60],{"class":59},"sVt8B","(factor):\n",[45,62,64,67,70],{"class":47,"line":63},2,[45,65,66],{"class":51},"    def",[45,68,69],{"class":55}," multiply",[45,71,72],{"class":59},"(n):\n",[45,74,76,79,82,85,88],{"class":47,"line":75},3,[45,77,78],{"class":51},"        return",[45,80,81],{"class":59}," n ",[45,83,84],{"class":51},"*",[45,86,87],{"class":59}," factor     ",[45,89,91],{"class":90},"sJ8bj","# factor is a free variable\n",[45,93,95,98],{"class":47,"line":94},4,[45,96,97],{"class":51},"    return",[45,99,100],{"class":59}," multiply\n",[45,102,104],{"class":47,"line":103},5,[45,105,107],{"emptyLinePlaceholder":106},true,"\n",[45,109,111,114,117,120,124],{"class":47,"line":110},6,[45,112,113],{"class":59},"double ",[45,115,116],{"class":51},"=",[45,118,119],{"class":59}," make_multiplier(",[45,121,123],{"class":122},"sj4cs","2",[45,125,126],{"class":59},")\n",[45,128,130,133,135,137,140],{"class":47,"line":129},7,[45,131,132],{"class":59},"triple ",[45,134,116],{"class":51},[45,136,119],{"class":59},[45,138,139],{"class":122},"3",[45,141,126],{"class":59},[45,143,145,148,151,154],{"class":47,"line":144},8,[45,146,147],{"class":59},"double(",[45,149,150],{"class":122},"5",[45,152,153],{"class":59},")     ",[45,155,156],{"class":90},"# 10\n",[45,158,160,163,165,167],{"class":47,"line":159},9,[45,161,162],{"class":59},"triple(",[45,164,150],{"class":122},[45,166,153],{"class":59},[45,168,169],{"class":90},"# 15\n",[15,171,172,173,176,177,180,181,184,185,187],{},"Even though ",[42,174,175],{},"make_multiplier"," has returned, ",[42,178,179],{},"double"," still remembers ",[42,182,183],{},"factor == 2",". Each\ncall to ",[42,186,175],{}," creates an independent closure with its own captured state.",[10,189,191],{"id":190},"inspecting-the-captured-variables","Inspecting the captured variables",[15,193,194,195,198,199,33],{},"The captured values live on the function object's ",[42,196,197],{},"__closure__",", and the names on\n",[42,200,201],{},"__code__.co_freevars",[35,203,205],{"className":37,"code":204,"language":39,"meta":40,"style":40},"double.__code__.co_freevars        # ('factor',)\ndouble.__closure__[0].cell_contents # 2\n",[42,206,207,221],{"__ignoreMap":40},[45,208,209,212,215,218],{"class":47,"line":48},[45,210,211],{"class":59},"double.",[45,213,214],{"class":122},"__code__",[45,216,217],{"class":59},".co_freevars        ",[45,219,220],{"class":90},"# ('factor',)\n",[45,222,223,225,227,230,233,236],{"class":47,"line":63},[45,224,211],{"class":59},[45,226,197],{"class":122},[45,228,229],{"class":59},"[",[45,231,232],{"class":122},"0",[45,234,235],{"class":59},"].cell_contents ",[45,237,238],{"class":90},"# 2\n",[15,240,241],{},"You rarely need this, but it proves the value is genuinely stored with the function, not\nrecomputed.",[10,243,245],{"id":244},"closures-capture-variables-not-values","Closures capture variables, not values",[15,247,248,249,252,253,256],{},"This is the crucial subtlety: a closure stores a reference to the ",[19,250,251],{},"variable",", and reads\nits current value ",[19,254,255],{},"when called",", not when defined. With a single value that's invisible;\nin a loop it causes the classic bug:",[35,258,260],{"className":37,"code":259,"language":39,"meta":40,"style":40},"funcs = []\nfor i in range(3):\n    funcs.append(lambda: i)\n\n[f() for f in funcs]    # [2, 2, 2] — all share the same i, now 2\n",[42,261,262,272,294,305,309],{"__ignoreMap":40},[45,263,264,267,269],{"class":47,"line":48},[45,265,266],{"class":59},"funcs ",[45,268,116],{"class":51},[45,270,271],{"class":59}," []\n",[45,273,274,277,280,283,286,289,291],{"class":47,"line":63},[45,275,276],{"class":51},"for",[45,278,279],{"class":59}," i ",[45,281,282],{"class":51},"in",[45,284,285],{"class":122}," range",[45,287,288],{"class":59},"(",[45,290,139],{"class":122},[45,292,293],{"class":59},"):\n",[45,295,296,299,302],{"class":47,"line":75},[45,297,298],{"class":59},"    funcs.append(",[45,300,301],{"class":51},"lambda",[45,303,304],{"class":59},": i)\n",[45,306,307],{"class":47,"line":94},[45,308,107],{"emptyLinePlaceholder":106},[45,310,311,314,316,319,321,324],{"class":47,"line":103},[45,312,313],{"class":59},"[f() ",[45,315,276],{"class":51},[45,317,318],{"class":59}," f ",[45,320,282],{"class":51},[45,322,323],{"class":59}," funcs]    ",[45,325,326],{"class":90},"# [2, 2, 2] — all share the same i, now 2\n",[15,328,329,330,334,335,338,339,341],{},"All three lambdas close over the ",[331,332,333],"em",{},"same"," ",[42,336,337],{},"i",", which is ",[42,340,123],{}," after the loop. The fix is to bind\nthe current value with a default argument (evaluated at definition time):",[35,343,345],{"className":37,"code":344,"language":39,"meta":40,"style":40},"funcs = [lambda i=i: i for i in range(3)]\n[f() for f in funcs]    # [0, 1, 2]\n",[42,346,347,381],{"__ignoreMap":40},[45,348,349,351,353,356,358,361,363,366,368,370,372,374,376,378],{"class":47,"line":48},[45,350,266],{"class":59},[45,352,116],{"class":51},[45,354,355],{"class":59}," [",[45,357,301],{"class":51},[45,359,360],{"class":59}," i",[45,362,116],{"class":51},[45,364,365],{"class":59},"i: i ",[45,367,276],{"class":51},[45,369,279],{"class":59},[45,371,282],{"class":51},[45,373,285],{"class":122},[45,375,288],{"class":59},[45,377,139],{"class":122},[45,379,380],{"class":59},")]\n",[45,382,383,385,387,389,391,393],{"class":47,"line":63},[45,384,313],{"class":59},[45,386,276],{"class":51},[45,388,318],{"class":59},[45,390,282],{"class":51},[45,392,323],{"class":59},[45,394,395],{"class":90},"# [0, 1, 2]\n",[15,397,398,399,402],{},"Using ",[42,400,401],{},"functools.partial(lambda i: i, i)"," works too — the point is to snapshot the value\neach iteration.",[10,404,406],{"id":405},"nonlocal-writing-to-a-captured-variable","nonlocal — writing to a captured variable",[15,408,409,410,413,414,417],{},"By default a closure can ",[19,411,412],{},"read"," a free variable but not rebind it (assignment would make\nit local). ",[42,415,416],{},"nonlocal"," lets the inner function modify the enclosing variable, which is how\nyou keep mutable state in a closure:",[35,419,421],{"className":37,"code":420,"language":39,"meta":40,"style":40},"def make_counter():\n    count = 0\n    def increment():\n        nonlocal count       # rebind the enclosing count\n        count += 1\n        return count\n    return increment\n\nc = make_counter()\nc(), c(), c()    # (1, 2, 3)\n",[42,422,423,433,443,452,463,474,481,488,492,502],{"__ignoreMap":40},[45,424,425,427,430],{"class":47,"line":48},[45,426,52],{"class":51},[45,428,429],{"class":55}," make_counter",[45,431,432],{"class":59},"():\n",[45,434,435,438,440],{"class":47,"line":63},[45,436,437],{"class":59},"    count ",[45,439,116],{"class":51},[45,441,442],{"class":122}," 0\n",[45,444,445,447,450],{"class":47,"line":75},[45,446,66],{"class":51},[45,448,449],{"class":55}," increment",[45,451,432],{"class":59},[45,453,454,457,460],{"class":47,"line":94},[45,455,456],{"class":51},"        nonlocal",[45,458,459],{"class":59}," count       ",[45,461,462],{"class":90},"# rebind the enclosing count\n",[45,464,465,468,471],{"class":47,"line":103},[45,466,467],{"class":59},"        count ",[45,469,470],{"class":51},"+=",[45,472,473],{"class":122}," 1\n",[45,475,476,478],{"class":47,"line":110},[45,477,78],{"class":51},[45,479,480],{"class":59}," count\n",[45,482,483,485],{"class":47,"line":129},[45,484,97],{"class":51},[45,486,487],{"class":59}," increment\n",[45,489,490],{"class":47,"line":144},[45,491,107],{"emptyLinePlaceholder":106},[45,493,494,497,499],{"class":47,"line":159},[45,495,496],{"class":59},"c ",[45,498,116],{"class":51},[45,500,501],{"class":59}," make_counter()\n",[45,503,505,508],{"class":47,"line":504},10,[45,506,507],{"class":59},"c(), c(), c()    ",[45,509,510],{"class":90},"# (1, 2, 3)\n",[15,512,513,514,516,517,520,521,524,525,528,529,532],{},"Without ",[42,515,416],{},", ",[42,518,519],{},"count += 1"," raises ",[42,522,523],{},"UnboundLocalError"," because the assignment makes\n",[42,526,527],{},"count"," local to ",[42,530,531],{},"increment",".",[10,534,536],{"id":535},"closures-vs-classes","Closures vs classes",[15,538,539],{},"A closure with state is essentially a tiny object with one method. When do you pick which?",[35,541,543],{"className":37,"code":542,"language":39,"meta":40,"style":40},"# Closure — concise when there's a single behaviour and a little state\ndef make_adder(n):\n    return lambda x: x + n\n\n# Class — clearer when there are multiple methods or richer state\nclass Adder:\n    def __init__(self, n):\n        self.n = n\n    def __call__(self, x):\n        return x + self.n\n    def reset(self):\n        ...\n",[42,544,545,550,559,575,579,584,595,605,617,627,642,653],{"__ignoreMap":40},[45,546,547],{"class":47,"line":48},[45,548,549],{"class":90},"# Closure — concise when there's a single behaviour and a little state\n",[45,551,552,554,557],{"class":47,"line":63},[45,553,52],{"class":51},[45,555,556],{"class":55}," make_adder",[45,558,72],{"class":59},[45,560,561,563,566,569,572],{"class":47,"line":75},[45,562,97],{"class":51},[45,564,565],{"class":51}," lambda",[45,567,568],{"class":59}," x: x ",[45,570,571],{"class":51},"+",[45,573,574],{"class":59}," n\n",[45,576,577],{"class":47,"line":94},[45,578,107],{"emptyLinePlaceholder":106},[45,580,581],{"class":47,"line":103},[45,582,583],{"class":90},"# Class — clearer when there are multiple methods or richer state\n",[45,585,586,589,592],{"class":47,"line":110},[45,587,588],{"class":51},"class",[45,590,591],{"class":55}," Adder",[45,593,594],{"class":59},":\n",[45,596,597,599,602],{"class":47,"line":129},[45,598,66],{"class":51},[45,600,601],{"class":122}," __init__",[45,603,604],{"class":59},"(self, n):\n",[45,606,607,610,613,615],{"class":47,"line":144},[45,608,609],{"class":122},"        self",[45,611,612],{"class":59},".n ",[45,614,116],{"class":51},[45,616,574],{"class":59},[45,618,619,621,624],{"class":47,"line":159},[45,620,66],{"class":51},[45,622,623],{"class":122}," __call__",[45,625,626],{"class":59},"(self, x):\n",[45,628,629,631,634,636,639],{"class":47,"line":504},[45,630,78],{"class":51},[45,632,633],{"class":59}," x ",[45,635,571],{"class":51},[45,637,638],{"class":122}," self",[45,640,641],{"class":59},".n\n",[45,643,645,647,650],{"class":47,"line":644},11,[45,646,66],{"class":51},[45,648,649],{"class":55}," reset",[45,651,652],{"class":59},"(self):\n",[45,654,656],{"class":47,"line":655},12,[45,657,658],{"class":122},"        ...\n",[15,660,661,662,665,666,668],{},"Use a ",[19,663,664],{},"closure"," for a single, simple captured behaviour (a callback, a configured\nfunction). Use a ",[19,667,588],{}," when you need multiple related methods, introspectable state, or\nthe object will grow.",[10,670,672],{"id":671},"recap","Recap",[15,674,675,676,679,680,683,684,688],{},"A closure is a nested function that remembers ",[19,677,678],{},"free variables"," from its enclosing scope,\ngiving each instance independent captured state — the basis of decorators and callbacks.\nClosures capture ",[19,681,682],{},"variables, not values",", so functions built in a loop all see the final\nvalue unless you snapshot it with a default argument. Use ",[19,685,686],{},[42,687,416],{}," to rebind a\ncaptured variable (e.g. a running counter). Choose a closure for one simple behaviour and a\nclass when state and methods grow.",[690,691,692],"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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":40,"searchDepth":63,"depth":63,"links":694},[695,696,697,698,699,700,701],{"id":12,"depth":63,"text":13},{"id":25,"depth":63,"text":26},{"id":190,"depth":63,"text":191},{"id":244,"depth":63,"text":245},{"id":405,"depth":63,"text":406},{"id":535,"depth":63,"text":536},{"id":671,"depth":63,"text":672},"What a Python closure is, how free variables are captured, the role of nonlocal, the late-binding loop gotcha, and when a closure beats a class.","hard","md","Python",{},"\u002Fblog\u002Fpython-closures-explained","\u002Fpython\u002Ffunctions\u002Fclosures",{"title":5,"description":702},"blog\u002Fpython-closures-explained","Closures & Scope","Functions","functions","2026-06-19","L22ygrqjjXjXPKSzkxmVjIwZMSIetuSIsOMUuBe6As8",1782244093367]