Compile Ready
Module 3 · Scope & Closures

Block Scope

Beginner7m read3m practice10m total
ScopeletconstLoops

Introduction

A block is a pair of curly braces: an if body, a for body, a while body, or even a standalone { ... }. Variables declared with let and const are scoped to the nearest block.

Block scope is one of the biggest practical improvements in modern JavaScript because it makes variables live exactly where readers expect them to live.

Why This Matters

Many classic interview bugs come from using var in loops and callbacks. let fixes those bugs not by magic, but by creating a fresh binding for each block or loop iteration. This is a must-know bridge from basic scope to closures.

Theory

let and const are block-scoped

A let or const binding exists only inside the closest { ... } block that contains it. Once execution leaves the block, that name is not available.

const means the binding cannot be reassigned

const prevents reassignment of the variable binding, not mutation of the value. A const object can still have properties changed; the variable just cannot be pointed at a different object.

The temporal dead zone

let and const are hoisted in a technical sense, but they are not usable before the declaration line runs. The region before the declaration is called the temporal dead zone (TDZ), and reading the variable there throws a ReferenceError.

Loop bindings

for (let i = 0; ...) creates a new i binding for each iteration. That is why callbacks created in a let loop remember the value from their own iteration. for (var i = 0; ...) uses one function-scoped i, so every callback shares the same final value.

PatternBinding behaviorCallback result
for (var i...)one shared function-scoped iall callbacks see final i
for (let i...)fresh i per iterationeach callback sees its own i
IIFE around varcaptures argument per calleach callback sees captured copy

Visual Diagrams

Block scope boundaries
function demo() scope
  var a  ----------------------------------- visible through whole function

  if (condition) {
    let b  -------- visible only inside this block
    const c ------- visible only inside this block
  }

  b and c are gone here
  a is still visible here

`let` and `const` stop at the block boundary; `var` stops at the function boundary.

Code Examples

`if` blocks hide `let` and `const`

The status binding exists only inside the if block. Use this to reduce accidental reuse.

Loading…

The loop closure bug and both fixes

The var version shares one binding. The let version creates per-iteration bindings. The IIFE version captures a value in a function argument.

Loading…

Output Prediction

Predict the output #1

javascript
1const varCallbacks = [];
2for (var i = 0; i < 3; i++) {
3 varCallbacks.push(function () {
4 return i;
5 });
6}
7
8const letCallbacks = [];
9for (let j = 0; j < 3; j++) {
10 letCallbacks.push(function () {
11 return j;
12 });
13}
14
15console.log(varCallbacks[0](), varCallbacks[1](), varCallbacks[2]());
16console.log(letCallbacks[0](), letCallbacks[1](), letCallbacks[2]());

Interview Questions

1Why does `let` fix the classic loop callback problem?

A for loop with let creates a new binding for the loop variable on each iteration. Each callback closes over its own iteration's binding. With var, there is one function-scoped binding shared by all callbacks, so they all see the final value after the loop finishes.

Asked at:GoogleAmazonApple

Follow-ups

  • How would you fix it before `let` existed?
  • What role does an IIFE play here?
2What is the difference between `const` and immutability?

const makes the variable binding non-reassignable. It does not freeze the value. A const object can still be mutated unless you also use techniques such as Object.freeze or immutable data structures.

Quiz

1. Why do callbacks created in `for (let i = 0; i < 3; i++)` see `0`, `1`, and `2` instead of all seeing `3`?

2. What happens if you read a `let` variable before its declaration line runs?

Summary

  • Blocks are `{ ... }` regions; `let` and `const` are scoped to the nearest block.
  • `var` ignores block scope and belongs to the nearest function or global scope.
  • `for (let ...)` creates a fresh binding per iteration, fixing the classic callback bug.
  • `const` prevents reassignment of the binding, not mutation of the value.