Suggest an editImprove this articleRefine the answer for “scope in JavaScript: types and working principles”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Scope** in JavaScript defines where a variable can be accessed. Three types: global (entire codebase), function (inside one function), and block (inside `{}`). ```javascript if (true) { var x = 1; // function scope, leaks out let y = 2; // block scope, stays in } console.log(x); // 1 console.log(y); // ReferenceError ``` **Key:** `let`/`const` are block-scoped; `var` is function-scoped and ignores blocks.Shown above the full answer for quick recall.Answer (EN)Image**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.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.