Predict exact JavaScript behavior for let and var by reasoning about block versus function scope, hoisting, temporal dead zones, redeclaration, and per-iteration loop closures.
Explain how `let` and `var` differ in scope, hoisting, redeclaration, and loop closures. Then predict the exact output or error for each practice snippet below and justify every line.
```javascript
function scopeDemo() {
console.log(x);
var x = 1;
if (true) {
let y = 2;
var z = 3;
}
console.log(z);
console.log(y);
}
scopeDemo();
```
```javascript
const a = [];
for (var i = 0; i < 3; i++) a.push(() => i);
const b = [];
for (let j = 0; j < 3; j++) b.push(() => j);
console.log(a.map(f => f()).join(","));
console.log(b.map(f => f()).join(","));
```
### Constraints & Assumptions
- Assume a modern JavaScript runtime and that each snippet runs independently.
- The first snippet runs inside a normal function; the second may run as a script or module because its relevant bindings are local to the loops.
- Distinguish declaration hoisting from initialization and distinguish a thrown error from printed output.
### Clarifying Questions to Ask
- Does execution stop after the first uncaught runtime error?
- Are the callbacks invoked during each iteration or after both loops have completed?
- Would a top-level binding distinction matter to either result?
### What a Strong Answer Covers
- Function scope for `var` and block scope for `let`.
- `var` initialization to `undefined` versus the temporal dead zone for `let`.
- The first snippet's printed values and the exact point at which its `ReferenceError` stops execution.
- One shared `var` loop binding versus a fresh per-iteration `let` binding in the second snippet.
- Redeclaration and global-object differences as relevant context, without substituting them for the line-by-line predictions.
### Follow-up Questions
- How does `const` compare with `let` for binding and object mutation?
- Why does an immediately invoked function fix the classic `var` loop closure bug?
- How do top-level bindings differ in an ES module?
Overview: Predict exact JavaScript behavior for let and var by reasoning about block versus function scope, hoisting, temporal dead zones, redeclaration, and per-iteration loop closures.
Explain how let and var differ in scope, hoisting, redeclaration, and loop closures. Then predict the exact output or error for each practice snippet below and justify every line.
function scopeDemo() {
console.log(x);
var x = 1;
if (true) {
let y = 2;
var z = 3;
}
console.log(z);
console.log(y);
}
scopeDemo();
const a = [];
for (var i = 0; i < 3; i++) a.push(() => i);
const b = [];
for (let j = 0; j < 3; j++) b.push(() => j);
console.log(a.map(f => f()).join(","));
console.log(b.map(f => f()).join(","));
Constraints & Assumptions
Assume a modern JavaScript runtime and that each snippet runs independently.
The first snippet runs inside a normal function; the second may run as a script or module because its relevant bindings are local to the loops.
Distinguish declaration hoisting from initialization and distinguish a thrown error from printed output.
Clarifying Questions to Ask Guidance
Does execution stop after the first uncaught runtime error?
Are the callbacks invoked during each iteration or after both loops have completed?
Would a top-level binding distinction matter to either result?
What a Strong Answer Covers Guidance
Function scope for
var
and block scope for
let
.
var
initialization to
undefined
versus the temporal dead zone for
let
.
The first snippet's printed values and the exact point at which its
ReferenceError
stops execution.
One shared
var
loop binding versus a fresh per-iteration
let
binding in the second snippet.
Redeclaration and global-object differences as relevant context, without substituting them for the line-by-line predictions.
Follow-up Questions Guidance
How does
const
compare with
let
for binding and object mutation?
Why does an immediately invoked function fix the classic
var
loop closure bug?