036
LVL 02 — CIRCUIT BREAKER SESSION 036 DAY 36

CLOSURES BUG

🎫 PIXELCRAFT-024
🐛 Bug | 🟡 Medium | Priority: 🔴 Critical

Moving any of the 8 filter sliders only changes saturation. The other 7 have no effect. Classic closure bug.
CONCEPTS.UNLOCKED
🔒
Closures
A function remembers the scope where it was created. Even after the outer function finishes, the inner function still has access to those variables. This is powerful — and dangerous.
🪤
The Loop Closure Trap
var is function-scoped — shared across all loop iterations. let is block-scoped — each iteration gets its own copy. This one-word difference causes one of JS's most notorious bugs.
🔗
Scope Chain
Global → function → block. When a variable is referenced, JS searches up the scope chain. A closure captures variables from its enclosing scope — not copies, but live references.
🏭
Factory Functions
Functions that create other functions with captured state. createFilterSlider(name, ...) returns a slider with name permanently baked in via closure.
🔐
Private State via Closures
Closures enable truly private variables: only the returned functions can access the enclosed data. No external code can read or modify it. Privacy without classes.
📸
Capturing vs Copying
Closures capture references, not values. If the variable changes after the closure is created, the closure sees the new value. This is why var in loops breaks — all callbacks share one variable.
HANDS-ON.TASKS
01
Reproduce in Isolation
// Create 5 buttons, each should log its index for (var i = 0; i < 5; i++) { const btn = document.createElement('button'); btn.textContent = `Button ${i}`; btn.addEventListener('click', function() { console.log(i); // ALL print 5! }); document.body.appendChild(btn); }
Every button logs 5. Not 0, 1, 2, 3, 4. Every. Single. One. The classic closure trap — one of JavaScript's most infamous bugs.
02
Understand WHY

var i is function-scoped, shared across all iterations. By the time any button is clicked, the loop has finished and i = 5.

All 5 click handlers close over the same variable. They don't capture the value at creation time — they capture a live reference to i.

03
Fix with let
for (let i = 0; i < 5; i++) { // `let` is block-scoped — each iteration // has its own `i` // ... same code ... // now logs 0, 1, 2, 3, 4 correctly! }
One word changes everything. let creates a new binding per iteration. Each closure captures its own copy. This is why let was added in ES6 — to fix exactly this problem.
04
Apply Fix to PixelCraft Sliders

Find the slider creation loop in PixelCraft. Replace var with let. Test all 8 sliders — each should now control its own filter independently.

05
Build a createFilterSlider Factory
function createFilterSlider(name, min, max, onChange) { const slider = document.createElement('input'); slider.type = 'range'; slider.min = min; slider.max = max; slider.value = 0; slider.addEventListener('input', function() { onChange(parseInt(slider.value)); // `name` is captured in this closure }); return slider; }
Each call to createFilterSlider creates a new closure. The name, min, max values are permanently captured for that specific slider. No loop variable sharing.
06
Create a Counter Factory
function createCounter() { let count = 0; // Private! Only the returned // functions can access it. return { increment: () => ++count, decrement: () => --count, getCount: () => count, }; }

count is truly private. No external code can access it. Only increment, decrement, and getCount — all closures — can read or modify it.

07
Close the Ticket
git switch -c bugfix/PIXELCRAFT-024-closure-bug git add src/scripts/ git commit -m "Fix slider closure bug, var → let + factory pattern (PIXELCRAFT-024)" git push origin bugfix/PIXELCRAFT-024-closure-bug # PR → Review → Merge → Close ticket ✅
CS.DEEP-DIVE

Closures exist in most modern languages.

A closure is a function + its lexical environment (the variables in scope when it was created).

// Closures enable:

Private state      → the counter example
Memoization        → cache results of
                     expensive computations
Currying           → partial function application
Module pattern     → encapsulation before ES Modules

// Closures in other languages:
Python, Swift, Kotlin, Rust

// Understanding closures is often what
// separates junior from mid-level developers
// in interviews and real codebases.
"Closure Lab"
[A] Build a memoize() function that caches the results of expensive computations: const fastFib = memoize(fibonacci). Second call with same args returns instantly.
[B] Create a rate limiter using closures: createRateLimiter(maxCalls, timeWindow) returns a function that only executes if fewer than maxCalls have occurred in the window.
[C] Find 3 examples of closures you've already used in PixelCraft without realizing it. (Hint: every callback in an event listener is a closure.)
REF.MATERIAL
ARTICLE
Mozilla Developer Network
The definitive guide to closures: lexical scoping, practical uses, the loop trap, and performance considerations.
CLOSURESMDNESSENTIAL
VIDEO
Fireship
Visual, rapid explanation of closures: scope chain, the loop trap, factory functions, and practical uses.
CLOSURESQUICK
VIDEO
Web Dev Simplified
Clear walkthrough: what closures are, how they work, the var trap, and practical patterns like factory functions.
CLOSURESPRACTICAL
ARTICLE
javascript.info
Deep dive: lexical environment objects, scope chain mechanics, garbage collection of closures, and the classic loop exercise.
CLOSURESINTERACTIVEDEEP
ARTICLE
Mozilla Developer Network
Why let was added to JavaScript: block scoping, temporal dead zone, and the var loop problem it was designed to fix.
LETSCOPEMDN
// LEAVE EXCITED BECAUSE
You debugged one of JavaScript's most infamous pitfalls. Closures "clicked" — and they'll never confuse you again.