๐ Closures
A closure is a function that remembers and can access variables from its enclosing function, even after the enclosing function has finished executing.
๐น Basic Example
def outer():
message = "Hello"
def inner():
print(message)
return inner
greet = outer()
greet() #HelloHow it works:

The inner() function is a closure because it remembers the variable message from outer().
๐น Closure with a Parameter
def multiplier(x):
def multiply(y):
return x * y
return multiply
double = multiplier(2)
triple = multiplier(3)
print(double(5)) #10
print(triple(5)) #15Here:
double โ remembers x = 2
triple โ remembers x = 3Even though multiplier() has finished executing, the returned functions remember their respective values of x.
๐ Example Counter
A closure is an inner function that remembers variables from its outer function even after the outer function finishes.
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter๐ How it works
counter = make_counter()countstarts at0make_counter()returnscountercounter()rememberscount
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3count keeps its value between calls because of the closure.
๐ nonlocal
nonlocal countmeans:
"Use and modify the
countfrom the outer function."
Closure = Inner function + remembered outer variable ๐
<div class="infographic-pyclosure">
<style>
.infographic-pyclosure {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--mono-font: 'IBM Plex Mono', 'SFMono-Regular', Menlo, monospace;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
box-sizing: border-box;
max-width: 680px;
margin: 0 auto;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
}
.infographic-pyclosure[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.infographic-pyclosure *, .infographic-pyclosure *::before, .infographic-pyclosure *::after { box-sizing: border-box; }
.infographic-pyclosure .cl-wrap { padding: 0 clamp(1rem, 4vw, 2rem) 1.5rem; }
.infographic-pyclosure h1 {
font-family: var(--display-font); font-weight: 600;
font-size: clamp(1.5rem, 5vw, 2.05rem); text-align: center; margin: 1.5rem 0 0.25rem; letter-spacing: -0.01em;
}
.infographic-pyclosure .cl-sub { text-align: center; color: var(--ink-soft); font-size: 0.86rem; margin: 0 0 1.4rem; }
.infographic-pyclosure .code-block {
font-family: var(--mono-font); font-size: 0.82rem; line-height: 1.7; white-space: pre;
background: var(--paper-alt); border: 1px solid var(--line); border-radius: 8px;
padding: 0.85rem 1rem; margin-bottom: 1.2rem;
}
.infographic-pyclosure .code-block .kw { color: var(--accent); font-weight: 700; }
.infographic-pyclosure .code-block .comment { color: var(--ink-soft); }
.infographic-pyclosure .ctl-row { display: flex; justify-content: center; gap: 0.6rem; margin-bottom: 1.3rem; flex-wrap: wrap; }
.infographic-pyclosure .add-btn {
font: inherit; font-family: var(--mono-font); font-size: 0.85rem; font-weight: 700;
background: var(--accent); color: #fff; border: none; border-radius: 999px;
padding: 0.55rem 1.2rem; cursor: pointer;
}
.infographic-pyclosure .add-btn:disabled { opacity: 0.5; cursor: default; }
.infographic-pyclosure .clear-btn {
font: inherit; font-size: 0.78rem; font-weight: 600; color: var(--ink-soft);
background: none; border: 1px solid var(--line); padding: 0.4rem 0.8rem; cursor: pointer; border-radius: 999px;
}
.infographic-pyclosure .counter-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.8rem; margin-bottom: 1rem; }
.infographic-pyclosure .counter-card {
border: 2px solid var(--accent); background: var(--accent-soft); border-radius: 10px;
padding: 0.9rem 1rem; display: flex; flex-direction: column; align-items: center; gap: 0.5rem;
min-width: 140px; animation: cl-pop-in 0.3s ease;
}
@keyframes cl-pop-in { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
.infographic-pyclosure .counter-name { font-family: var(--mono-font); font-size: 0.78rem; font-weight: 700; color: var(--accent); }
.infographic-pyclosure .counter-value {
font-family: var(--display-font); font-size: 2.2rem; font-weight: 600; color: var(--ink); line-height: 1;
transition: transform 0.15s;
}
.infographic-pyclosure .counter-value.is-pulsing { animation: cl-bump 0.3s ease; }
@keyframes cl-bump { 0% { transform: scale(1); } 45% { transform: scale(1.25); } 100% { transform: scale(1); } }
.infographic-pyclosure .counter-call-btn {
font: inherit; font-family: var(--mono-font); font-size: 0.78rem; font-weight: 700;
background: var(--paper); color: var(--accent); border: 1.5px solid var(--accent); border-radius: 6px;
padding: 0.35rem 0.7rem; cursor: pointer;
}
.infographic-pyclosure .cl-caption {
text-align: center; font-size: 0.85rem; color: var(--ink-soft); min-height: 2.4em;
}
.infographic-pyclosure .cl-caption strong { color: var(--ink); font-family: var(--mono-font); }
@media (prefers-reduced-motion: reduce) {
.infographic-pyclosure *, .infographic-pyclosure *::before, .infographic-pyclosure *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="cl-wrap">
<p class="cl-sub">A function that remembers the variables around it, even after that scope is gone.</p>
<div class="code-block"><span class="kw">def</span> make_counter():
count = 0
<span class="kw">def</span> counter():
<span class="kw">nonlocal</span> count
count += 1
<span class="kw">return</span> count
<span class="kw">return</span> counter <span class="comment"># count lives on inside it</span></div>
<div class="ctl-row">
<button class="add-btn" id="addBtn">Run: counter_1 = make_counter()</button>
<button class="clear-btn" id="clearBtn">โบ Clear all</button>
</div>
<div class="counter-grid" id="counterGrid"></div>
<p class="cl-caption" id="clCaption">Each call to make_counter() creates a brand-new count โ sealed inside its own counter().</p>
</div>
<script>
(function () {
var root = document.currentScript.closest('.infographic-pyclosure')
|| document.querySelector('.infographic-pyclosure');
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) root.setAttribute('data-theme', 'dark');
var addBtn = root.querySelector('#addBtn');
var clearBtn = root.querySelector('#clearBtn');
var counterGrid = root.querySelector('#counterGrid');
var clCaption = root.querySelector('#clCaption');
var MAX_COUNTERS = 4;
var counters = []; // { id, name, count, valueEl, cardEl }
function updateAddBtn() {
var nextIdx = counters.length + 1;
addBtn.textContent = 'Run: counter_' + nextIdx + ' = make_counter()';
addBtn.disabled = counters.length >= MAX_COUNTERS;
}
function updateCaption() {
if (counters.length === 0) {
clCaption.innerHTML = 'Each call to make_counter() creates a brand-new count โ sealed inside its own counter().';
} else if (counters.length === 1) {
clCaption.innerHTML = 'Tap "call ' + counters[0].name + '()" a few times โ it remembers its own count between calls.';
} else {
var parts = counters.map(function (c) { return '<strong>' + c.name + '</strong> = ' + c.count; }).join(', ');
clCaption.innerHTML = parts + ' โ every counter has its own private count. Calling one never touches the others.';
}
}
function addCounter() {
if (counters.length >= MAX_COUNTERS) return;
var idx = counters.length + 1;
var name = 'counter_' + idx;
var card = document.createElement('div');
card.className = 'counter-card';
card.innerHTML =
'<div class="counter-name">' + name + '()</div>' +
'<div class="counter-value">0</div>' +
'<button class="counter-call-btn">call ' + name + '()</button>';
counterGrid.appendChild(card);
var valueEl = card.querySelector('.counter-value');
var btnEl = card.querySelector('.counter-call-btn');
var state = { id: idx, name: name, count: 0, valueEl: valueEl };
counters.push(state);
btnEl.addEventListener('click', function () {
state.count++;
valueEl.textContent = state.count;
valueEl.classList.remove('is-pulsing');
void valueEl.offsetWidth;
valueEl.classList.add('is-pulsing');
updateCaption();
});
updateAddBtn();
updateCaption();
}
addBtn.addEventListener('click', addCounter);
clearBtn.addEventListener('click', function () {
counters = [];
counterGrid.innerHTML = '';
updateAddBtn();
updateCaption();
});
addCounter();
})();
</script>
</div>โญ Why Use Closures?
Closures are useful for:
๐ Data hiding
โ๏ธ Creating customized functions
๐จ Decorators
๐งฉ Maintaining state between function calls
โญ Remember
Closure = A function + the variables it remembers from its enclosing scope.
def outer(x):
def inner():
return x
return innerHere, inner() remembers x even after outer() has finished.