Facebook Pixel

Hoisting in Loops and Conditionals in JavaScript

var and let behave very differently inside loops and if-blocks. Here is how hoisting interacts with blocks.

Hoisting in Loops and Conditionals in JavaScript

Block scoping changes how hoisting behaves. var and let/const interact with loops and conditionals very differently.

var in a Loop Leaks Out

for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // logs: 3, 3, 3 console.log(i); // 3

var i is function-scoped (or global), so there is one i shared by all iterations. By the time the timeouts fire, i is 3.

let in a Loop Creates a New Binding Per Iteration

for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } // logs: 0, 1, 2

let i is block-scoped, and the spec says each iteration gets a fresh binding. Each closure captures its own i.

var Inside an if Block Leaks

if (true) { var x = 5; } console.log(x); // 5

var ignores block boundaries (except function bodies). x is hoisted to the enclosing function or global scope.

let Inside an if Block Does Not Leak

if (true) { let y = 5; } console.log(y); // ReferenceError

let is block-scoped. y lives only inside the if block.

Function Declarations in Blocks

Function declarations inside blocks are hoisted to the enclosing block scope in ES6+ (not the function scope). Behavior varies in older engines, so avoid relying on it. Use function expressions instead:

if (condition) { const doWork = () => { ... }; doWork(); }

TDZ in Loops

for (let i = i; i < 3; i++) {} // ReferenceError

The right-hand i is in the TDZ until the declaration completes.

Closures and Loops

The classic var loop + setTimeout + closure bug is the most famous hoisting-related interview question. The fix is let, or an IIFE to capture the current value:

for (var i = 0; i < 3; i++) { (function (j) { setTimeout(() => console.log(j), 0); })(i); } // logs: 0, 1, 2

The Takeaway

var in loops and conditionals leaks to the enclosing function/global. let creates a fresh binding per iteration and per block. Use let in loops to fix closure bugs, and let/const in blocks to keep variables confined.

Because var is function-scoped. There is one i shared by all iterations. By the time the setTimeout callbacks run, the loop has finished and i equals the final value, so all callbacks log that value.

Because let is block-scoped and the spec gives each iteration a fresh binding. Each closure captures its own copy of i, so callbacks log 0, 1, 2 instead of the final value.

Yes. var ignores block boundaries (except function bodies), so a var inside an if is hoisted to the enclosing function or global scope. let and const are block-scoped and do not leak.

Use let instead of var (each iteration gets a fresh binding), or wrap the body in an IIFE that captures the current value of i as a parameter.

In ES6+, function declarations in blocks are hoisted to the block scope, not the function scope. Behavior varied in older engines, so prefer function expressions with const for predictable behavior.

Ready to master React completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.