JavaScript uses both block scope and function scope depending on declaration type. var is function-scoped, while let and const are block-scoped. Strong answers explain loop behavior, closure bugs, switch-case pitfalls, and module-vs-script global scope differences.
Is JavaScript block-scoped or function-scoped?
Short answer first
JavaScript is both, depending on what you declare with:
varis function-scoped (or global if declared outside a function).letandconstare block-scoped (inside{ ... }).
So the right interview answer is not one word. It is: 'Both, depending on declaration keyword.'
Keyword | Scope type | Redeclare in same scope? | Access before declaration | Typical bug |
|---|---|---|---|---|
| Function scope | Yes | Allowed (value is | Leaking outside blocks and loop-closure issues |
| Block scope | No | ReferenceError (TDZ) | Expecting it to behave like |
| Block scope | No | ReferenceError (TDZ) | Assuming object values become immutable |
if (true) {
var a = 1;
let b = 2;
const c = 3;
}
console.log(a); // 1 (var escapes block)
// console.log(b); // ReferenceError
// console.log(c); // ReferenceError
Loop behavior: the classic interview trap
var in loops creates one shared binding for all iterations. let creates a fresh binding per iteration. This is why asynchronous callbacks behave differently.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var', i), 0);
}
// var 3
// var 3
// var 3
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log('let', j), 0);
}
// let 0
// let 1
// let 2
Function scope vs block scope in real code
A function body is also a block, but var ignores inner blocks inside that function and stays visible across the entire function body. let/const stay where they are declared.
function demo(flag) {
if (flag) {
var status = 'ok';
let temp = 42;
}
console.log(status); // 'ok' when flag=true
// console.log(temp); // ReferenceError
}
demo(true);
Switch/case pitfall
All case clauses share one switch block unless you add braces. With let/const, duplicate names across cases can throw errors unless each case is wrapped in its own block.
const kind = 'a';
switch (kind) {
case 'a': {
const msg = 'A';
console.log(msg);
break;
}
case 'b': {
const msg = 'B';
console.log(msg);
break;
}
}
Global nuance: Script vs Module
In classic scripts, top-level var can attach to window. In ES modules, top-level bindings are module-scoped and do not become window properties. This matters in bundlers and interview follow-ups.
// Classic script (non-module)
var legacy = 1;
let modern = 2;
console.log(window.legacy); // 1
console.log(window.modern); // undefined
Practical rules
- Default to
const; useletonly when reassignment is required. - Avoid
varin modern codebases unless maintaining legacy code. - For loops with async callbacks, prefer
let. - In interviews, say 'JavaScript supports both block and function scope' and then explain keyword behavior.
Practical scenario
A dashboard uses var inside loops that register click handlers, causing every handler to point at the last row index in production.
Common pitfalls
- Mixing
varandletin the same function. - Ignoring TDZ behavior during refactors.
- Assuming switch cases create separate scope automatically.
Add tests for loop callbacks and branch-local variables, especially when migrating old code from
var to let/const.Think of var as office-wide access in a function floor, while let/const are room-specific badges. Same building, different permission boundaries.
JavaScript is not purely block-scoped or purely function-scoped. It is keyword-dependent: var is function-scoped; let/const are block-scoped. Most production bugs come from legacy var assumptions in loops, conditionals, and mixed old/new code.
Use the relevant interview-question hub first, then move into a concrete study plan before targeted company sets.