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:
varbecomeslet. "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
letiteration gets its own binding.In this lesson I cover the patterns you will actually get asked about:
constby default, whytypeofthrows in the TDZ, whyswitchcases trip on duplicatelet, and the famous loop-closure flip.If you wrote
varanywhere last week — this lesson is your refactor checklist.Read the full lesson -> [link]
#JavaScript #InterviewPrep #ES6 #LetConst #BlockScope #TDZ #CodingInterview
What You'll Learn
- Why
constby default is the right discipline (and what it does NOT guarantee) - Temporal Dead Zone edge cases — including why
typeofthrows forlet/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
Common Mistakes
- Assuming
constmeans deep immutability — it only freezes the binding.const arr = []; arr.push(1);is perfectly legal. UseObject.freezeor 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 eventypeofis not safe. - Declaring
let/constin multipleswitchcases without block braces, then being confused bySyntaxError: Identifier 'x' has already been declared. Wrap each case body in{ ... }.
Interview Questions
Q: Why should you prefer const over let?
constsignals intent — this binding won't change. It prevents accidental reassignment, makes code easier to reason about, and helps with optimization. Note thatconstdoesn'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/constdeclaration being processed. Accessing the variable in this zone throws a ReferenceError — even withtypeof. This differs from undeclared variables wheretypeofsafely returns"undefined".
Q: Why do you need curly braces in switch cases with let/const?
All
caseclauses share the same block scope by default. Usinglet/constin 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.
letcreates a fresh binding forion every loop iteration. Each closure produced inside the loop captures its own copy. Withvar, there is a single function-scopedishared 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.