Block Scope vs Function Scope
Rooms Inside the House
Scenario
Quick test: does this print
3orReferenceError?function demo() { for (var i = 0; i < 3; i++) {} console.log(i); } demo();It prints
3. The loop ended, the{ }closed, butvar idoesn't care - it leaked right out of theforblock into the enclosing function scope. Swapvarforletand you'd getReferenceError.This one difference between
varandlet/constexplains half the modern JavaScript style guide. It explains whyfor (let i ...)withsetTimeoutprints0, 1, 2whilefor (var i ...)prints3, 3, 3. It explains why senior engineers tell juniors to "just stop usingvar." And it explains why standalone{ }blocks are suddenly useful.
Block Scope vs Function Scope - Rooms Inside the House
JavaScript has two primary types of scoping: function scope (created by functions) and block scope (created by {} blocks like if, for, while, and standalone blocks).
Think of function scope like a house - everything inside the house is accessible from any room. Block scope is like individual rooms within that house - what's in a room stays in that room.
Function Scope - var
var is function-scoped. It only respects function boundaries, completely ignoring block boundaries:
function functionScoped() {
if (true) {
var x = "I exist everywhere in this function";
}
for (var i = 0; i < 3; i++) {
// loop body
}
console.log(x); // "I exist everywhere in this function"
console.log(i); // 3 -- leaked out of the for loop!
}
functionScoped();
var variables only get a new scope when they're inside a function:
function outer() {
var a = "outer";
function inner() {
var a = "inner"; // different variable -- new function = new scope
console.log(a); // "inner"
}
inner();
console.log(a); // "outer" -- not affected by inner's `a`
}
outer();
Block Scope - let and const
let and const respect every {} block:
function blockScoped() {
let a = "function level";
if (true) {
let a = "block level"; // different variable -- new block = new scope
const b = "also block level";
console.log(a); // "block level"
console.log(b); // "also block level"
}
console.log(a); // "function level"
// console.log(b); // ReferenceError -- b doesn't exist here
}
blockScoped();
Standalone Blocks
You can create blocks without any control structure - useful for limiting variable scope:
{
const temp = computeExpensiveValue();
// use temp here
console.log(temp);
}
// temp is gone -- clean and contained
// console.log(temp); // ReferenceError
The Practical Difference - Loop Variables
// var: ONE variable shared across all iterations
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 100);
}
// var: 3, 3, 3
// let: FRESH variable per iteration
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log("let:", i), 100);
}
// let: 0, 1, 2
With let, the JS engine creates a new binding for i in each loop iteration. Each setTimeout callback closes over its own separate i. With var, there's only one i shared by all callbacks.
Comparison Table
| Feature | Function Scope (var) | Block Scope (let/const) |
|---|---|---|
| Created by | function keyword only | Any {} block |
Leaks out of if/for | Yes | No |
| Loop iteration isolation | No (shared variable) | Yes (new variable per iteration) |
| Hoisting behavior | undefined | TDZ |
| Re-declaration in same scope | Allowed | Error |
Common Mistakes
- Using
varinside aforloop with async callbacks and expecting each iteration to have its own counter - you'll get the final value every time. - Treating
{ ... }as a scope boundary forvar- it isn't. Onlyfunctionboundaries containvar. - Re-declaring a
letorconstin the same scope thinking it's harmless - it's aSyntaxError, unlikevarwhich silently allows re-declaration.
Interview Questions
Q: What's the difference between block scope and function scope?
Function scope is created only by the
functionkeyword - variables declared withvarare confined to the nearest enclosing function. Block scope is created by any{ }block (likeif,for, or a standalone block) - variables declared withletorconstare confined to that block.
Q: Why does var in a for loop leak outside the loop?
Because
varis function-scoped, not block-scoped. Theforloop's{}is not a function boundary, sovarignores it. The variable is scoped to the nearest enclosing function (or global scope if there's no function).
Q: Can you create a block scope without an if or for statement?
Yes. A standalone
{ ... }block creates a fresh block scope. Anyletorconstdeclared inside it is not visible outside the braces, which is a clean way to limit the lifetime of temporary variables.
Q: Which keywords create block-scoped variables?
letandconst. Both respect every{ }block, includingif,for,while,switch, and standalone blocks.varignores block boundaries and only respects function boundaries.
Q: Why does for (let i ...) with setTimeout print 0, 1, 2 but for (var i ...) print 3, 3, 3?
With
let, the engine creates a fresh binding forion every iteration, so eachsetTimeoutcallback closes over its own distincti. Withvar, there is a singleishared across all iterations; by the time the callbacks run, the loop has finished andihas its final value.