JavaScript Interview Prep
ES6+ Features

let, const, Block Scoping

Patterns and Edge Cases

LinkedIn Hook

Interviewer: "What prints?"

for (var i = 0; i < 3; i++) setTimeout(() => console.log(i), 0);

You say "0, 1, 2."

They smile and change one word: var becomes let. "And now?"

If you don't know the difference — you just failed a classic senior-level probe on block scoping, the Temporal Dead Zone, and how each let iteration gets its own binding.

In this lesson I cover the patterns you will actually get asked about: const by default, why typeof throws in the TDZ, why switch cases trip on duplicate let, and the famous loop-closure flip.

If you wrote var anywhere last week — this lesson is your refactor checklist.

Read the full lesson -> [link]

#JavaScript #InterviewPrep #ES6 #LetConst #BlockScope #TDZ #CodingInterview


let, const, Block Scoping thumbnail


What You'll Learn

  • Why const by default is the right discipline (and what it does NOT guarantee)
  • Temporal Dead Zone edge cases — including why typeof throws for let/const
  • The switch-case duplicate-declaration trap and the classic loop-closure flip

Patterns That Come Up in Interviews

We covered let, const, and var in detail in Blog 1. Here we focus on patterns and edge cases that come up in interviews.

Pattern: const by Default

// Default to const — only use let when you KNOW the value will change
const API_URL = "https://api.example.com";
const users = []; // const doesn't mean immutable — the array can be mutated
users.push("Rakibul"); // works fine

// Use let for values that change
let count = 0;
count++;

// Never use var in modern code

TDZ Edge Cases

The Temporal Dead Zone is the region between entering a block and the let/const declaration being reached:

// TDZ with typeof — even typeof throws!
{
  // TDZ starts for 'x'
  console.log(typeof x); // ReferenceError!
  let x = 10;
  // TDZ ends
}

// Compare with undeclared variable
console.log(typeof undeclared); // "undefined" — no error for undeclared

// TDZ in conditional
function example(condition) {
  if (condition) {
    // TDZ for 'value' exists from block start to declaration
    console.log(value); // ReferenceError if reached before declaration
    let value = "hello";
    return value;
  }
}

Block Scoping in Switch Statements

A common gotcha — switch cases share the same block:

// This throws a SyntaxError
switch (action) {
  case "create":
    let result = createItem(); // Error: 'result' already declared
    break;
  case "delete":
    let result = deleteItem(); // Duplicate declaration!
    break;
}

// Fix: wrap each case in its own block
switch (action) {
  case "create": {
    let result = createItem();
    console.log(result);
    break;
  }
  case "delete": {
    let result = deleteItem(); // Different block scope — no conflict
    console.log(result);
    break;
  }
}

The Classic Loop Closure

// var — all callbacks share the same i
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 3, 3, 3

// let — each iteration gets its own i
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 0, 1, 2

let, const, Block Scoping visual 1


Common Mistakes

  • Assuming const means deep immutability — it only freezes the binding. const arr = []; arr.push(1); is perfectly legal. Use Object.freeze or immutable data structures if you need true immutability.
  • Treating the TDZ like hoisting-with-undefined. It is hoisting — the binding exists — but reading or writing it before the declaration throws ReferenceError, and even typeof is not safe.
  • Declaring let/const in multiple switch cases without block braces, then being confused by SyntaxError: Identifier 'x' has already been declared. Wrap each case body in { ... }.

Interview Questions

Q: Why should you prefer const over let?

const signals intent — this binding won't change. It prevents accidental reassignment, makes code easier to reason about, and helps with optimization. Note that const doesn't make values immutable — objects and arrays can still be mutated.

Q: What is the TDZ and why does typeof throw for let/const?

The Temporal Dead Zone is the period from entering a block to the let/const declaration being processed. Accessing the variable in this zone throws a ReferenceError — even with typeof. This differs from undeclared variables where typeof safely returns "undefined".

Q: Why do you need curly braces in switch cases with let/const?

All case clauses share the same block scope by default. Using let/const in multiple cases without braces causes a duplicate declaration error. Wrapping each case in {} creates a separate block scope for each case.

Q: Explain why for (let i = ...) "fixes" the classic closure loop bug.

let creates a fresh binding for i on every loop iteration. Each closure produced inside the loop captures its own copy. With var, there is a single function-scoped i shared by every iteration, so all closures see the final value.


Quick Reference — Cheat Sheet

let / const / var — QUICK MAP

Scope:
  var     -> function-scoped
  let     -> block-scoped
  const   -> block-scoped

Hoisting:
  var     -> hoisted, initialized to undefined
  let     -> hoisted, TDZ until declaration
  const   -> hoisted, TDZ until declaration

Re-assignment:
  var     -> allowed, re-declare too
  let     -> re-assign yes, re-declare no (same block)
  const   -> re-assign NO (binding frozen; value can still mutate)

Gotchas:
  - typeof in TDZ throws ReferenceError
  - switch cases share one block -> wrap each case in { }
  - for (let i ...) -> fresh binding per iteration (closure-safe)

Default rule of thumb:
  const > let > (never) var

Previous: Default Parameters -> Clean Defaults Without Falsy Traps Next: Modules -> import, export, and Dynamic Loading


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

On this page