JavaScript Scope, Hoisting, and Execution Context

Understanding variable resolution and execution phases in JavaScript

By Hank Kim

JavaScript Scope, Hoisting, and Execution Context

Understanding variable resolution and execution phases in JavaScript

JavaScript Scope, Hoisting, and Execution Context

In this document, I want to explain how scope, hoisting, and execution context interact in JavaScript, and how variable references are resolved during execution.

1. Scope

Scope defines the region of code where a variable is accessible.

  • JavaScript has function scope and block scope.

    • var → function scope only
    • let and const → block scope ({})
  • Accessing variables outside their scope results in a ReferenceError.

JavaScript follows lexical scope rules:

  • Scope is determined at code-writing time by where variables are defined, not where functions are called.
  • Inner scopes can access variables from outer scopes.
  • Outer scopes cannot access variables from inner scopes.

2. Hoisting

Hoisting is the behavior where declarations are processed before execution. This gives the impression that variables and functions are “moved” to the top of their scope.

  • var: declaration is hoisted and initialized to undefined.
  • let and const: declarations are hoisted but remain uninitialized. Accessing them before initialization causes a ReferenceError due to the Temporal Dead Zone (TDZ).
console.log(a); // undefined
var a = 10;

console.log(b); // ReferenceError
let b = 20;

Hoisting exists because JavaScript engines parse code before executing it. During parsing, declarations are recorded in memory, allowing them to be referenced earlier than their written position.


3. Variable Declarations (var, let, const)

  • var

    • Function scope
    • Hoisted and initialized with undefined
    • Redeclaration allowed
  • let

    • Block scope
    • Hoisted but not initialized (TDZ applies)
    • Redeclaration not allowed
  • const

    • Block scope
    • Hoisted but not initialized (TDZ applies)
    • Must be initialized at declaration

4. Execution Context and Lexical Environment

Whenever code runs, the JavaScript engine creates an execution context.

Global execution context in JavaScript runtime

  • Each execution context contains a Lexical Environment with:

    • Environment Record → stores variables and their bindings
    • Outer Lexical Environment Reference → points to the parent scope

5. Variable Resolution Process

When a variable is referenced:

  1. The engine looks in the current scope’s environment record.
  2. If not found, it follows the outer lexical environment reference.
  3. This continues until the global scope.
  4. If not found globally, a ReferenceError is thrown.

This explains why inner scopes can access outer variables, but outer scopes cannot access inner variables.

function outer() {
  const a = 1;
  function inner() {
    console.log(a); // 1 (found in outer scope)
  }
  inner();
}
outer();

console.log(a); // ReferenceError (outer cannot access inner)