var, let, and const output questions look like tricks, but they follow fixed rules about scope and hoisting. Memorising “prefer const” is not enough when an interviewer asks why var escaped a block or why reading let before its declaration threw an error. Build the rules first, then trace any snippet line by line.
Three declarations, three behaviours
Five behaviours separate them, and almost every output question turns on one of the five:
Behaviour |
|
|
|
|---|---|---|---|
Scope | Function or global | Block | Block |
Hoisting | Hoisted and initialised to | Hoisted but uninitialised in the TDZ | Hoisted but uninitialised in the TDZ |
Same-scope redeclaration | Allowed | Syntax error | Syntax error |
Reassignment | Allowed | Allowed | Not allowed |
Initial value required | No | No | Yes |
In a classic browser script, a top-level var x also creates a property on window. Top-level let and const do not. Modules have their own top-level scope, and Node.js has a different wrapper model, so do not turn the browser-script rule into a universal claim.
The modern default is const for a binding that will not be reassigned and let when reassignment is intentional. Understanding var still matters because existing code and interview snippets use it heavily.
Function scope versus block scope
A block is the region inside braces for constructs such as if, for, and while. let and const stay inside that region. var ignores the block boundary and belongs to the nearest function.
function f() {
if (true) {
var x = 1;
let y = 2;
}
console.log(x);
console.log(y);
}
f();The first console.log prints 1 because x belongs to the whole function. The second reaches for y outside its block, where no binding exists at all, so it throws ReferenceError: y is not defined. Execution stops at that line. Hold on to that exact message, because reading a let binding too early throws a different one.
Braces alone do not constrain var:
{
var visible = "outside too";
const local = "inside only";
}This leakage is one reason block-scoped declarations make refactoring safer. A temporary loop or condition variable cannot silently overwrite another binding elsewhere in the function.
Hoisting and the Temporal Dead Zone
JavaScript creates bindings before it executes the statements in a scope. That setup is commonly called hoisting, but the three declarations are not initialised in the same way.
console.log(a);
var a = 1;The var a binding exists from the start of the scope and is initialised to undefined. The log therefore prints undefined; assignment to 1 happens later.
Now compare let:
console.log(b);
let b = 2;The binding also exists from the start of its block, but it remains uninitialised until execution reaches the declaration. Reading it in that interval throws ReferenceError: Cannot access 'b' before initialization. That interval is the Temporal Dead Zone, or TDZ. const follows the same early-read rule.
The TDZ explains why “let is not hoisted” is an inaccurate shortcut. If there were no binding, scope lookup might continue outward. Instead, the inner uninitialised binding blocks access until its declaration runs.
Even typeof does not bypass the TDZ:
typeof missingName; // "undefined" if no such binding exists
typeof tdzName; // ReferenceError
let tdzName = 1;The loop closure trap
Callbacks created in a loop make the scope difference visible.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}There is one function-scoped i shared by all three arrows. The loop schedules three callbacks while i takes values 0, 1, and 2. The increment after the third iteration changes it to 3, the condition 3 < 3 fails, and only then do the timer callbacks run. All three read the same final binding, so the output is 3, 3, 3.
Use let instead:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}The loop creates a fresh i binding for each iteration. The three closures capture 0, 1, and 2 separately, so the output is 0, 1, 2.
Count the bindings and both outputs follow. Three iterations create three callbacks. With var, one shared cell ends at 3, producing three copies of 3. With let, three cells hold one value each, producing 0, 1, and 2.

What const actually makes constant
const prevents reassignment of the binding. It does not recursively freeze the value stored in an object.
const arr = [1, 2];
arr.push(3); // allowed
console.log(arr); // [1, 2, 3]
arr = []; // TypeError: Assignment to constant variableThe array object can still be mutated, so push succeeds. The name arr cannot be redirected to a different array, so reassignment fails. Likewise, properties of a const object can change unless another mechanism prevents it.
Object.freeze(value) provides shallow freezing: direct properties cannot be changed in the usual way, but nested objects are not automatically frozen. Also, const x; raises SyntaxError: Missing initializer in const declaration, because a constant binding must receive its value at the declaration itself.
Scope and hoisting traps interviewers use
Repeating
var xin the same function is allowed, which can hide an accidental redeclaration. Repeatinglet xorconst xin the same scope is a syntax error.A
vardeclared at top level becomes awindowproperty only in the classic browser-script setting, not in an ES module.typeofthrows when the chosen binding exists but is still in its TDZ.A
varloop callback reads the final shared value unless another scope is introduced.A function declaration is available with its function value during scope setup. A function expression assigned to
varhas only the variable initialised toundefinedbefore assignment, so calling it early throws aTypeError.
These are the same output-reading habits tested in JavaScript interview questions for freshers. For the closure machinery underneath the loop trap, including two correct ways to fix the var version, work through Closures in JavaScript: lexical scope and the var loop trap.
The short version and next step
var is function-scoped and starts as undefined during scope setup. let and const are block-scoped and cannot be read in their TDZ. const protects a binding from reassignment, not the internals of an object.
The KnowledgeGate question bank carries over 70 JavaScript questions built on these snippet patterns. Follow the wider Placement Preparation category, build the language foundation in Complete JavaScript, or practise along the Mera Placement Hoga bundle. For every output question, mark the scope, creation point, initialisation point, and execution order before answering.




