Skip to main content

scope in JavaScript: types and working principles

Scope in JavaScript defines where a variable can be read or written. The engine resolves scope at compile time, not at runtime. That is why it's called lexical scope.

Theory

TL;DR

  • Scope = the region of code where a variable exists and can be accessed
  • Three types: global (entire codebase), function (inside one function), block (inside one {})
  • var respects function scope only; let and const respect block scope
  • Inner scopes can read outer variables, but not the reverse
  • Use let/const in the smallest block possible; avoid var

Quick example

javascript
const globalVar = 'global'; // global scope function outer() { const outerVar = 'outer'; // function scope if (true) { let blockVar = 'block'; // block scope console.log(globalVar); // "global" - reads up the chain console.log(outerVar); // "outer" - reads up the chain console.log(blockVar); // "block" - local } console.log(blockVar); // ReferenceError - block is gone } outer(); console.log(outerVar); // ReferenceError - function scope is gone

Each scope can read from its parent, but the parent cannot reach into the child.

Scope chain

JavaScript uses lexical scoping: scope is determined by where code is written, not where it runs. When the engine looks up a variable, it starts in the current scope and walks up the chain toward global until it finds the variable or throws a ReferenceError.

V8 creates a LexicalEnvironment object for each scope during compilation. These objects link to their parent via an outer reference, forming the chain. Variable lookups follow those links one step at a time.

When to use

  • Global scope: app-wide constants like process.env.NODE_ENV or a shared config object
  • Function scope: isolate logic inside a utility or handler so it doesn't affect other functions
  • Block scope: limit a variable to a for loop, if block, or try/catch without letting it leak

The narrower the scope, the fewer surprises in code review.

var, let, and const

var binds to the nearest function scope (or global if outside all functions). It ignores blocks entirely. let and const bind to the nearest block. They're also subject to the Temporal Dead Zone (TDZ): the variable exists in scope from the start of the block, but any access before the declaration line throws a ReferenceError.

javascript
console.log(typeof a); // "undefined" - var hoisted, initialized to undefined console.log(typeof b); // ReferenceError - let is in TDZ var a = 1; let b = 2;

This trips up developers with years of experience. I've seen it cause confusion in code reviews when someone assumes typeof is always safe before a declaration.

Common mistakes

var in a loop

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

All three callbacks close over the same i because var is function-scoped. By the time they run, the loop is done and i is 3. Fix: use let, which creates a new binding per iteration.

Assuming var respects blocks

javascript
if (true) { var x = 5; } console.log(x); // 5 - leaks out of the block

var only stops at function boundaries. Use let or const here.

Shadowing without noticing

javascript
const user = 'Alice'; function greet() { const user = 'Bob'; // shadows outer user console.log(user); // "Bob" } greet(); console.log(user); // "Alice"

Shadowing is valid JavaScript, but it makes the code harder to trace. Rename the inner variable when possible.

Real-world usage

  • React: useEffect closes over component scope variables. If users changes, a new effect captures the fresh reference
  • Express: route handlers close over the app object from module scope
  • Node.js: require wraps each module in a function, giving it its own scope for exports
  • Redux thunks: capture dispatch from the connected component's scope

Follow-up questions

Q: What does console.log(x); var x = 1; print?
A: undefined. The declaration is hoisted to the top of the function scope, but the initialization stays in place.

Q: Why does for (var i...) with setTimeout print the same number three times?
A: var creates one binding shared across all iterations. All closures point to the same i. Use let to get a separate binding per iteration.

Q: What is the Temporal Dead Zone?
A: TDZ is the period between when a let/const variable enters scope (start of the block) and when the declaration line is reached. Any access in that window throws a ReferenceError.

Q: Does top-level let create a global variable?
A: No. var x = 1 at the top level adds x to window in a browser. let x = 1 creates a block-scoped variable that is not a property of window.

Q: How does V8 optimize scope lookups in hot loops?
A: V8 uses inline caching. After the first lookup, the engine records the type and location of the variable. Subsequent accesses in the same scope skip the chain walk entirely. Keeping variables in tight scopes helps this optimization.

Examples

Basic: scope chain in action

javascript
const language = 'JavaScript'; function describe() { const topic = 'scope'; function announce() { const detail = 'lexical'; console.log(`${detail} ${topic} in ${language}`); // "lexical scope in JavaScript" // Each variable comes from a different scope level } announce(); } describe();

announce reads detail from its own scope, topic from its parent function, and language from global. The lookup always goes from inside out, never the other way.

Intermediate: var vs let in a loop

This comes up in JavaScript interviews constantly. Two versions, two different outputs:

javascript
// var: all closures share one binding for (var i = 0; i < 3; i++) { setTimeout(() => console.log('var:', i), 0); } // Output: var: 3 var: 3 var: 3 // let: each iteration gets its own binding for (let j = 0; j < 3; j++) { setTimeout(() => console.log('let:', j), 0); } // Output: let: 0 let: 1 let: 2

The difference is entirely scope rules. var is function-scoped, so there's one i for the whole loop. let is block-scoped, so each loop body gets a fresh j that belongs to that iteration only.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?