Suggest an editImprove this articleRefine the answer for “Execution context in JavaScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Execution context** in JavaScript is the runtime environment the engine creates to run a piece of code. It holds variables, the `this` binding, and a reference to the outer scope. ```javascript var x = 1; // global execution context function fn() { let y = 2; // fn's own execution context } ``` **Key:** every function call creates a new context with two phases: creation (hoisting, memory allocation) and execution (code runs line by line). The call stack tracks all active contexts in LIFO order.Shown above the full answer for quick recall.Answer (EN)Image**Execution context** is the runtime environment JavaScript creates for every piece of code it runs, bundling together variables, the `this` binding, and access to the outer scope. ## Theory ### TL;DR - Think of it like a worker's desk: each function call gets its own desk (context) with its own notes (variables), knows who the manager is (outer scope), and tracks the current task (`this`). - Three types: global (one per script), function (one per call), eval (inside `eval()`). - Two phases per context: creation phase allocates memory, execution phase runs code. - `var` hoists to `undefined` in the creation phase; `let`/`const` stay uninitialized (Temporal Dead Zone). - Contexts stack on the call stack. Inner contexts access outer ones via scope chain, not the other way around. ### Quick example ```javascript console.log(x); // undefined - var hoisted in creation phase var x = 1; console.log(x); // 1 function test() { console.log(y); // ReferenceError - let stays in TDZ let y = 2; } test(); ``` `var x` gets allocated and set to `undefined` before any code runs. `let y` inside `test()` exists in memory but is off-limits until that exact line executes. That gap is the Temporal Dead Zone. ### What an execution context contains Every context, global or function, shares the same internal structure: - **LexicalEnvironment** stores `let`, `const`, and function declarations, plus a reference to the outer environment (scope chain). - **VariableEnvironment** stores `var` declarations and function arguments. - **ThisBinding** holds whatever `this` points to in this context. In the browser, the global context sets `this` to `window`. In Node.js, it sets `this` to `globalThis` (or `{}` inside a module). ### The two phases **Creation phase** runs before a single line of code executes. The engine scans the scope, allocates memory, and: - sets `var` declarations to `undefined` - marks `let`/`const` as uninitialized (TDZ) - stores full function declarations (not expressions) in memory right away **Execution phase** then runs code top to bottom. Variables get their actual values, functions get called, and each call pushes a new context onto the call stack. ### The call stack The call stack is a LIFO structure. When a function is called, a new context goes on top. When it returns, that context pops off. ```javascript function first() { second(); } function second() { third(); } function third() { console.log("third"); } first(); // Stack at deepest point: // [Global] -> [first()] -> [second()] -> [third()] ``` V8 (Chrome and Node.js) supports roughly 10,000 frames before throwing `RangeError: Maximum call stack size exceeded`. Infinite recursion hits that ceiling fast. ### Key difference: LexicalEnvironment vs VariableEnvironment Both exist inside every execution context, but they serve different purposes. `let`, `const`, and function declarations go into `LexicalEnvironment`. `var` and `arguments` go into `VariableEnvironment`. The practical consequence: `var` initializes to `undefined` during creation, so reading it before its line gives `undefined`. `let`/`const` do not initialize, so reading them before their line throws. [Closures](/questions/closures-in-javascript) work because an inner function's `LexicalEnvironment` keeps a reference to its outer context's environment. That reference stays alive even after the outer function returns. ### When to use this knowledge - Debugging a `this is undefined` error in a callback: recreate the execution context mentally to see what `this` was at call time. - Explaining why a `var` variable reads as `undefined` instead of throwing: creation phase, see [hoisting](/questions/hoisting-in-javascript). - Investigating a stale closure in React: the hook captured variables from an earlier render's context. - Tracing nested function scope: follow the scope chain up through parent contexts. ### How V8 handles this internally V8 parses code into an Abstract Syntax Tree first. For each execution context, the Ignition interpreter builds LexicalEnvironment and VariableEnvironment records, then resolves `this`. For async generators, Ignition suspends the context on `yield` and resumes it with the same environment intact, without unwinding the call stack. That is how `await` works too: the function context pauses and hands control back to the [event loop](/questions/event-loop-in-javascript), which schedules resumption via the microtask queue. ### Common mistakes **Assuming `let` hoists like `var`:** ```javascript console.log(name); // ReferenceError, not undefined let name = 'Alice'; ``` `let` is hoisted in the sense that the engine registers its existence, but it does not initialize until that line runs. Reading it before then throws. I have seen this trip up experienced developers who switch between `var` and `let` in legacy codebases. **Losing `this` in a nested callback:** ```javascript const obj = { path: '/users', handle: function(req, res) { setTimeout(function() { console.log(this.path); // undefined - new context, this = global }, 0); } }; ``` Arrow functions fix this. They do not create their own `this` binding and inherit it from the enclosing lexical context. ```javascript setTimeout(() => { console.log(this.path); // '/users' }, 0); ``` **Expecting `window` as `this` in strict mode:** ```javascript 'use strict'; function fn() { console.log(this); // undefined, not window } fn(); ``` Strict mode sets function context `this` to `undefined` when the function is called without an explicit receiver. **Stack overflow from deep recursion:** ```javascript function factorial(n) { return n <= 1 ? 1 : n * factorial(n - 1); } factorial(10000); // RangeError in V8 (~10k frame limit) ``` Each call pushes a new function execution context. Use a loop or a trampoline pattern for deep recursion. ### Real-world usage - React: every component render is a function call. Hooks like `useState` and `useCallback` work because their closures retain the component function's context from that render cycle. - Express: each route handler runs in its own function context. Arrow functions in middleware preserve `this` from the outer scope. - Node.js ESM: each module file runs in its own function-like context, giving private scope without polluting `globalThis`. - Redux: reducers access state via closure from the store's context, not via `this`. ### Follow-up questions **Q:** What happens during the creation phase? **A:** The engine allocates memory for all declarations in scope. `var` variables get `undefined`, full function declarations are stored, and `let`/`const` are marked uninitialized. `this` is bound, and the reference to the outer environment is set. **Q:** How does a closure relate to execution context? **A:** When an inner function is defined, its `LexicalEnvironment` stores a reference to the outer function's environment. That reference survives after the outer function returns. That surviving reference is the closure. **Q:** What is the Temporal Dead Zone? **A:** The period from the start of the creation phase until the line where `let` or `const` is initialized. During that window, the variable exists in memory but reading it throws a `ReferenceError`. **Q:** What is the difference between global context in a browser and Node.js? **A:** In a browser, `this` in the global context equals `window`. In Node.js CommonJS modules, `this` at the top level is `{}`, not `global`. The `global` object is accessible, but it is not the same as `this` inside a module. **Q:** Walk through how V8 handles an async generator in terms of execution context. **A:** Ignition suspends the generator's execution context on each `yield`. The context stays in memory (not on the call stack) and resumes when the generator is iterated again. For `async` functions, `await` does the same: the context pauses, and the microtask queue schedules resumption with the original environment intact. ## Examples ### Basic: hoisting in the creation phase ```javascript function outer() { console.log(a); // undefined - var hoisted during creation var a = 1; function inner() { console.log(a); // 1 - reads from outer context via scope chain } inner(); } outer(); // Output: // undefined // 1 ``` `outer` is called, a new function execution context is created. During the creation phase, `a` is hoisted to `undefined`. By execution time, `a` becomes `1`. When `inner` runs, it has no `a` of its own, so it walks the scope chain to `outer`'s context and finds `a = 1`. ### Intermediate: stale closure in a React effect ```javascript import { useState, useEffect } from 'react'; function Counter() { const [count, setCount] = useState(0); useEffect(() => { const interval = setInterval(() => { // Captures 'count' from the context of the render when this effect first ran. // If count was 0 then, it stays 0 here - stale closure. console.log(count); // always logs 0 if deps array is [] }, 1000); return () => clearInterval(interval); }, []); // empty deps: runs once, captures initial render context return <button onClick={() => setCount(c => c + 1)}>{count}</button>; } ``` The `setInterval` callback closes over `count` from the context of the first render. That context is frozen at `count = 0`. Later renders create new function contexts with updated `count`, but the interval never sees them. Fix: add `count` to the dependency array, or use `setCount(c => c + 1)` to avoid needing `count` in the closure. ### Advanced: `this` context loss in Express handlers ```javascript const express = require('express'); const router = express.Router(); router.get('/users', function(req, res) { // Regular function: this = router setTimeout(function() { // New execution context: this = global (or undefined in strict mode) console.log(this); // {} or undefined res.json({ ok: true }); }, 0); }); // Fix: arrow function inherits this from the enclosing context router.get('/users-fixed', function(req, res) { setTimeout(() => { console.log(this); // router - inherited from route handler's context res.json({ ok: true }); }, 0); }); ``` The arrow function inside `setTimeout` does not create its own execution context for `this`. It takes `this` from the closest regular function above it, which is the route handler. That is the whole mechanism behind why arrow functions solve `this` loss in callbacks.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.