Scope and Closures
In this chapter you will learn how JavaScript decides which variables your code can see, and why a function can "remember" variables from where it was created.
Prerequisites: You should be comfortable declaring variables with let and const, and defining and calling functions from earlier chapters.
What Is Scope?
Scope is the set of rules that decides where a variable is visible in your code. When JavaScript runs, it looks up variables in nested "layers":
- Global scope — variables declared at the top level of your script. They are visible everywhere.
- Function scope — variables declared inside a function. They are visible only inside that function (and in functions defined inside it).
- Block scope — variables declared with
letandconstinside curly braces{ }. They are visible only within those braces.
const global = 'I am global';
function example() {
const inside = 'I live in the function';
if (true) {
const insideBlock = 'I live in the block';
console.log(global, inside, insideBlock); // all three work
}
// console.log(insideBlock); // Error! Not visible here
}
example();Think of scope like rooms in a house. A thing left in the kitchen can be seen from the kitchen. Someone in the bedroom cannot see it.
What Is a Closure?
A closure is a function bundled together with the variables from the place where it was created. Even after that outer function finishes running, the inner function keeps access to those variables.
function makeCounter() {
let count = 0; // private variable
return function increment() {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3makeCounter finishes running the first time you call it. Yet counter still remembers count and keeps increasing it. That is the closure at work.
Show diagram source
flowchart LR
A[makeCounter called] --> B[count = 0 created]
B --> C[returns increment function]
C --> D[increment keeps access to count]
D --> E[each call to counter updates count]Why Closures Matter
Closures let you create private state — data that cannot be changed from outside, only through the functions you provide. This is the foundation of many patterns you will meet later, such as event handlers that remember settings, and modules that hide internal details.
function makeGreeter(name) {
return () => `Hello, ${name}!`;
}
const greetAda = makeGreeter('Ada');
const greetGrace = makeGreeter('Grace');
console.log(greetAda()); // Hello, Ada!
console.log(greetGrace()); // Hello, Grace!Each greeter remembers its own name, because each closure captures its own copy of the variable.
Common Mistakes
- Forgetting that
constandletare block-scoped. A variable declared inside an{ }block is not visible outside it. - Expecting a closure to see a variable that did not exist when the function was created. Closures capture variables from their birthplace, not from wherever they get called.
- Creating many closures in a loop and being surprised they share (or don't share) variables. With
let, each loop iteration gets its own copy — withvar, they all share one.
Exercise
What will console.log(counter()) print the fourth time you call it, after three calls that printed 1, 2, and 3?
Show answer
4. The closure keeps count alive between calls, so each call increases the same hidden variable.
Exercise
Why does the block-scoped variable insideBlock cause an error when used outside the if block?
Show answer
let and const create block-scoped variables, visible only inside the { } braces where they are declared. Outside the block, the variable does not exist.
Spotted a mistake, or code that does not work?