JavaScript Interview Prep
Scope & Closures

Block Scope vs Function Scope

Rooms Inside the House

LinkedIn Hook

Quick test: does this print 3 or ReferenceError?

function demo() {
  for (var i = 0; i < 3; i++) {}
  console.log(i);
}
demo();

It prints 3. The loop ended, the { } closed, but var i doesn't care — it leaked right out of the for block into the enclosing function scope. Swap var for let and you'd get ReferenceError.

This one difference between var and let/const explains half the modern JavaScript style guide. It explains why for (let i ...) with setTimeout prints 0, 1, 2 while for (var i ...) prints 3, 3, 3. It explains why senior engineers tell juniors to "just stop using var." And it explains why standalone { } blocks are suddenly useful.

In this lesson you'll learn the difference between function scope and block scope, which keywords respect which boundaries, and how per-iteration bindings make let play nicely with closures.

Read the full lesson -> [link]

#JavaScript #InterviewPrep #BlockScope #VarLetConst #Frontend #CodingInterview #JSFundamentals


Block Scope vs Function Scope thumbnail


What You'll Learn

  • The difference between function scope (var) and block scope (let / const)
  • Why var leaks out of if, for, and standalone { } blocks
  • How per-iteration bindings make let behave correctly with closures

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

FeatureFunction Scope (var)Block Scope (let/const)
Created byfunction keyword onlyAny {} block
Leaks out of if/forYesNo
Loop iteration isolationNo (shared variable)Yes (new variable per iteration)
Hoisting behaviorundefinedTDZ
Re-declaration in same scopeAllowedError

Block Scope vs Function Scope visual 1


Common Mistakes

  • Using var inside a for loop 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 for var — it isn't. Only function boundaries contain var.
  • Re-declaring a let or const in the same scope thinking it's harmless — it's a SyntaxError, unlike var which silently allows re-declaration.

Interview Questions

Q: What's the difference between block scope and function scope?

Function scope is created only by the function keyword — variables declared with var are confined to the nearest enclosing function. Block scope is created by any { } block (like if, for, or a standalone block) — variables declared with let or const are confined to that block.

Q: Why does var in a for loop leak outside the loop?

Because var is function-scoped, not block-scoped. The for loop's {} is not a function boundary, so var ignores 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. Any let or const declared 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?

let and const. Both respect every { } block, including if, for, while, switch, and standalone blocks. var ignores 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 for i on every iteration, so each setTimeout callback closes over its own distinct i. With var, there is a single i shared across all iterations; by the time the callbacks run, the loop has finished and i has its final value.


Quick Reference — Cheat Sheet

BLOCK vs FUNCTION SCOPE -- QUICK MAP

var      -> function scope  (ignores { } blocks)
let      -> block scope     (respects { } blocks)
const    -> block scope     (respects { } blocks, no reassign)

Block boundaries that only let/const respect:
  if { }, for { }, while { }, switch { }, { } standalone

Loop iteration bindings:
  for (var i ...)  -> single shared i
  for (let i ...)  -> new i per iteration

Re-declaration in same scope:
  var   -> allowed (silently)
  let   -> SyntaxError
  const -> SyntaxError

Previous: Closures -> The Function That Remembers Next: Module Scope -> Apartments Instead of a Shared Room


This is Lesson 2.4 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.

On this page