Loading...
Loading...
Where variables live, and how functions remember their environment.
Functions are reusable blocks of logic — the primary unit of organization in JavaScript. Parameters pass data in, return values send results out, and closures let functions remember their environment long after they were created.
A closure lets a function remember variables from where it was created — even after the outer function returns. See how scope chains, lexical binding, and closure patterns enable private state and callbacks.
Functions are the building blocks of every JavaScript application. Instead of repeating the same logic, you wrap it in a named block, pass in data, and get a result back. Arrow functions, default parameters, and rest/spread syntax make modern function code concise and readable.
This module also introduces closures — one of JavaScript's most powerful and misunderstood features. Interactive diagrams show how functions retain access to variables from their outer scope, which is essential for callbacks, event handlers, and module patterns.
Closures power React hooks, debounce utilities, module patterns, and event handlers. They are also the topic senior engineers use to tell deep JavaScript knowledge from surface-level familiarity — and they explain many confusing debugger moments.
Where variables are visible and accessible.
🌍 Global Scope
const APP_NAME = "VisualJS"; // accessible everywhere
📦 Function Scope
function greet() { const message = "Hello"; // only inside greet() }
🧱 Block Scope
if (true) { let x = 10; // only inside this {} const y = 20; }
var
Function-scoped
let
Block-scoped
const
Block-scoped
Inner scopes look outward until they find the variable.
const global = "G"; function outer() { const outerVar = "O"; function inner() { const innerVar = "I"; console.log(innerVar); // "I" — found locally console.log(outerVar); // "O" — found in outer console.log(global); // "G" — found in global } inner(); } // inner → outer → global (chain lookup)
💡 Scope lookup goes outward only. Outer scopes can never access inner variables. Siblings can't access each other.
A function that remembers its surrounding scope even after it executes.
function createCounter() { let count = 0; // enclosed variable return function() { count++; return count; }; }
createCounter() defines a variable and returns a function that references it.
// Live closure — count persists between clicks:
count is private — only counter() can access it
Real-world uses you'll see everywhere.
1. Private State (Module Pattern)
function createWallet(initial) { let balance = initial; return { deposit: (amount) => balance += amount, withdraw: (amount) => balance -= amount, getBalance: () => balance, }; } const wallet = createWallet(100); wallet.deposit(50); // balance = 150 wallet.getBalance(); // 150 // wallet.balance → undefined (private!)
2. Function Factory
function multiply(factor) { return (number) => number * factor; } const double = multiply(2); const triple = multiply(3); double(5); // 10 triple(5); // 15
3. Memoization / Caching
function memoize(fn) { const cache = new Map(); return (...args) => { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = fn(...args); cache.set(key, result); return result; }; }
Watch out for these closure mistakes.
// The classic loop bug: for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Output: 3, 3, 3 ← all same!
var is function-scoped. All callbacks share the SAME 'i' variable, which is 3 by the time they run.
Common questions about scope & closures.
Step through the code and watch variables change
1function createCounter() {2 let count = 0;34 return function increment() {5 count++;6 return count;7 };8}910const counter = createCounter();11console.log(counter());12console.log(counter());
No output yetThe createCounter function is stored in memory. Nothing executes yet.
Test your understanding — 3 questions
What will this output?
function outer() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = outer();
console.log(counter());
console.log(counter());