ReviseAlgo Logo

Scope & Closures

Block Scope & Lexical Scope

Master JavaScript scoping rules. Understand block scope limits with let/const, nested block rules, and lexical scope compilation vs execution binding.

Last Updated: July 15, 2026 10 min read

1. Introduction

Scoping rules dictate variable access across nested code blocks. JavaScript uses two main scoping mechanisms: Block Scope (introduced in ES6 with let and const) and Lexical Scope (static binding determined at compile time).

2. Why It Matters

Understanding lexical scope is key to understanding closures. Knowing how block scope works prevents variable leaking outside of loops and conditional blocks.

3. Real-World Analogy

Think of Nested Storage Boxes:

  • Block Scope (Mini Box): A tiny container labeled "screws" placed inside a larger drawer. You can only use those screws while working inside that specific container. If you step away from the drawer, you cannot see or reach the screws.
  • Lexical Scope (Factory Blueprints): The factory floor layout is set during construction (compilation). No matter who walks through the building later, the relative positions of the offices and rooms remain exactly as they were laid out on the blueprints.

4. How It Works

Let's explore both scoping rules:

1. Block Scope:

A block is code wrapped in curly braces { ... }. Variables declared with let or const inside a block are scoped to that block and cannot be accessed from the outside. Variables declared with var are not block-scoped.

2. Lexical Scope:

Also known as Static Scope, lexical scope means variable resolution is determined by where variables and blocks are physically defined in the source code, not where they are called from.

5. Architectural Details

Lexical scoping is resolved during compilation. When a function is compiled, it receives an internal property called [[Environment]] that points to the lexical environment of its parent scope. This ensures that no matter where the function is executed, it always resolves references based on its original lexical location.

6. Practical Example

This example demonstrates block scoping inside a for loop:

7. Common Mistakes

  • Assuming loops share a single index variable when using let: let inside a for loop header creates a new binding for each iteration. In contrast, var binds to a single variable across the entire loop, leading to unexpected results in asynchronous callbacks.
  • Confusing lexical scope with dynamic scope: Expecting variables to resolve based on the execution path rather than the compilation path.

8. Quick Quiz

Q1: Which variable declaration keyword is function-scoped but ignores block boundaries?

A) let

B) var

Answer: B — The var keyword is function-scoped and does not respect block boundaries (like if statements or loops).

9. Scenario-Based Challenge

The Dynamic Config Shadow:

An application configuration utility uses nested scopes. If you declare const port = 80 in the parent block, and let port = 8080 in an inner block, explain how variable shadowing rules handle values inside and outside the blocks.

10. Debugging Exercise

Explain why this code logs "3, 3, 3" instead of "0, 1, 2", and how to fix it:

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

Diagnosis: Since var is not block-scoped, there is only one global i variable shared across all iterations. By the time the asynchronous setTimeout callbacks run, the loop has completed and i has been incremented to 3.

Fix: Change var to let to create a new, block-scoped variable binding for each loop iteration:

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

11. Interview Questions

🟢 Q1: Contrast Lexical Scope with Dynamic Scope.

Answer: Lexical scope is determined at compile time based on the physical position of variables and blocks in the source code. Dynamic scope is resolved at runtime based on the execution call stack path. JavaScript uses lexical scope, meaning functions always resolve references based on their compile-time definition scope, not their call site.

12. Production Considerations

  • Block Boundaries: Use curly braces to isolate temporary calculations or variables inside complex methods, allowing memory to be freed as soon as execution exits the block.