JavaScript Execution Context
Deep dive into how JavaScript runs your code — execution context, creation and execution phases, the call stack, lexical environment, GEC vs FEC, and stack vs heap memory.

Introduction
Every line of JavaScript you write eventually runs inside an execution context — the environment the engine creates to manage variables, this, scope, and the order in which code executes. Understanding execution context is not optional trivia; it is the foundation behind scope, closures, hoisting, and the this keyword.
This guide walks from source code to machine execution:
- What execution context is and why it matters
- How the engine processes code before it runs
- Lexical environment vs execution context
- Global and function execution contexts
- Creation and execution phases
- The call stack
- Stack vs heap memory
Each section includes runnable examples you can paste into the browser console or Node.js.
Course Reference
Quick index
1. What is execution context?
An execution context is the environment in which JavaScript code is evaluated and executed. Think of it as a container the engine builds for a specific chunk of code — global script, a function body, or an eval block.
Every context has three essential components:
| Component | Role |
|---|---|
| Variable environment | Stores var declarations, function declarations, and let/const bindings |
| Lexical environment | Resolves identifiers through scope chains |
this binding | Determines what this refers to inside the current code |
When JavaScript starts running a file, it does not jump straight into line-by-line execution. It first creates a context, sets up memory for variables and functions, then runs the code inside that context.
const greeting = 'Hello'
function sayHello(name) {
const message = `${greeting}, ${name}!`
console.log(message)
}
sayHello('Kazi')
// "Hello, Kazi!"Running this script involves at least two contexts: one for the global script and one created each time sayHello is invoked.
2. Why it matters
Execution context is the invisible infrastructure behind several concepts you use every day.
Scope and closures
Scope is not a static label on a block of code — it is determined by the lexical environment attached to an execution context. Closures exist because a function retains access to the lexical environment from the context where it was created, even after that outer context has finished executing.
function createCounter() {
let count = 0
return function () {
count++
return count
}
}
const counter = createCounter()
console.log(counter()) // 1
console.log(counter()) // 2The inner function closes over the variable environment of createCounter's execution context.
The this keyword
this is not resolved by where a function is written — it is bound when the function's execution context is created. That binding depends on how the function is called (global, method, constructor, call/apply/bind, arrow function).
const user = {
name: 'Kazi',
greet() {
console.log(this.name)
},
}
user.greet() // 'Kazi' — `this` is `user`
const fn = user.greet
fn() // undefined in strict mode — `this` is not `user`Hoisting and temporal dead zone
During the creation phase, the engine allocates memory for declarations before any line runs. That is why function declarations appear available before their definition line, and why let/const exist in a temporal dead zone until their declaration line executes.
3. How the engine processes code
Before JavaScript executes anything, the engine transforms your source code through several stages.
Source Code → Tokenization → AST → Compilation → Machine Code → Execution| Stage | What happens |
|---|---|
| Source code | The .js file or <script> you write |
| Tokenization | Code is broken into tokens (const, =, 'hello', {, }) |
| AST | Tokens form an Abstract Syntax Tree — a structured representation |
| Compilation | Modern engines (V8, SpiderMonkey) use JIT — compile hot paths to machine code |
| Execution | Machine code runs inside execution contexts on the call stack |
// What you write
function add(a, b) {
return a + b
}
add(2, 3)The engine does not execute this character by character. It parses the entire function into an AST, compiles it, then creates an execution context when add is called.
4. Lexical environment vs execution context
These two terms are related but not interchangeable.
| Term | Focus |
|---|---|
| Lexical environment | Where identifiers are resolved — scope chain, outer references |
| Execution context | How the current code runs — phases, this, stack management |
A lexical environment is a record with:
- An environment record (variable bindings)
- A reference to an outer lexical environment (parent scope)
An execution context contains a lexical environment plus the this binding and other runtime metadata.
const outer = 'global'
function outerFn() {
const middle = 'middle'
function innerFn() {
const inner = 'inner'
console.log(inner, middle, outer)
}
innerFn()
}
outerFn()
// 'inner' 'middle' 'global'When innerFn runs, its lexical environment links outward: innerFn → outerFn → global. The execution context for innerFn uses that chain to resolve middle and outer.
5. Global execution context (GEC)
The Global Execution Context is created automatically when a JavaScript program starts. There is exactly one GEC per runtime (per tab in the browser, per Node.js process).
| Environment | Global object | this in global scope |
|---|---|---|
| Browser | window | window (non-strict) |
| Node.js | global | global (non-strict) |
| Modules | Module scope (not window) | undefined in ES modules |
// Browser (non-module script)
var title = 'Blog'
console.log(window.title) // 'Blog'
function showGlobal() {
console.log(this === window) // true in browser, non-strict
}
showGlobal()In ES modules (type="module"), top-level variables are scoped to the module — they do not become properties of window.
// ES module
const apiUrl = 'https://api.example.com'
// window.apiUrl → undefined6. Creation and execution phases
Every execution context — global or function — goes through two phases.
Phase 1: Creation
Before any statement runs, the engine:
- Creates the variable and lexical environments
- Sets up the scope chain (outer reference)
- Determines
thisbinding - Allocates memory for declarations
During creation:
| Declaration type | Memory behavior during creation phase |
|---|---|
var | Allocated and initialized to undefined |
function declaration | Fully stored (hoisted with body) |
let / const | Allocated but not initialized (TDZ) |
console.log(typeof hoistedFn) // 'function'
console.log(typeof hoistedVar) // 'undefined'
hoistedFn() // works — function body is available
// console.log(hoistedLet) // ReferenceError — TDZ
var hoistedVar
function hoistedFn() {
return 'I was hoisted'
}
let hoistedLet = 'not yet'Interactive: creation vs execution
Creation vs Execution Phase
Toggle phases and step through execution to see hoisting, TDZ, and when values become available.
source.js
Memory after creation — no user code has run yet
undefined
ƒ hoistedFn() { return "I was hoisted" }
⟨uninitialized⟩
Temporal Dead Zone — access throws ReferenceError
In creation, `function` declarations are fully hoisted; `var` is `undefined`; `let`/`const` exist but cannot be read until their line runs.
Phase 2: Execution
The engine runs code line by line:
- Assigns actual values to variables
- Executes expressions and function bodies
- Creates new function execution contexts on calls
var score = 10 // creation: score = undefined → execution: score = 10
function double(n) {
return n * 2
}
var result = double(score) // FEC created → runs → returns 20
console.log(result) // 207. Function execution context (FEC)
A Function Execution Context is created every time a function is invoked — not when it is defined.
| Context | When created | How many exist |
|---|---|---|
| GEC | Program start | One per runtime |
| FEC | Function call | One per active invocation |
function multiply(a, b) {
const product = a * b
return product
}
multiply(3, 4) // FEC #1 — arguments: a=3, b=4
multiply(5, 2) // FEC #2 — separate context, separate `product`Each FEC gets:
- Its own argument object / parameter bindings
- A local variable environment
- Its own
thisbinding (unless arrow function — inherits from enclosing lexicalthis) - A link to the outer lexical environment (closure)
function createGreeter(prefix) {
return function (name) {
// FEC for this inner function links to createGreeter's environment
return `${prefix}, ${name}!`
}
}
const sayHi = createGreeter('Hi')
console.log(sayHi('Kazi')) // 'Hi, Kazi!'Even after createGreeter returns and its FEC is popped off the stack, the inner function keeps a reference to prefix through the closure.
8. The call stack
The call stack (execution context stack) is a LIFO structure that tracks which context is currently running.
┌─────────────────┐
│ innerFn() FEC │ ← top (currently executing)
├─────────────────┤
│ outerFn() FEC │
├─────────────────┤
│ GEC │ ← bottom
└─────────────────┘Rules:
- Push a new context when a function is called
- Pop a context when a function returns
- Only the top context executes at any moment
function a() {
console.log('a start')
b()
console.log('a end')
}
function b() {
console.log('b start')
c()
console.log('b end')
}
function c() {
console.log('c')
}
a()Output:
a start
b start
c
b end
a endStack timeline:
- GEC runs → calls
a()→ push FEC fora acallsb()→ push FEC forbbcallsc()→ push FEC forccreturns → popcbreturns → popbareturns → popa
Interactive: call stack playground
Call Stack Playground
Step through nested function calls and watch contexts push onto and pop off the LIFO stack.
Program starts — Global Execution Context (GEC) is created and pushed
nested-calls.js
Call stack (top → bottom)
← currently executing
↑ push on call · pop on return ↓
Console output
—
Only the frame at the top of the stack runs code. When a function returns, its context is popped and control returns to the frame below.
9. Stack vs heap memory
JavaScript uses two memory regions. Execution contexts and the call stack interact with both.
| Memory | Stores | Access speed |
|---|---|---|
| Stack | Primitives, context frames, reference addresses | Fast, fixed size per frame |
| Heap | Objects, arrays, functions, closures (referenced data) | Larger, managed by GC |
// Stack: primitive values and references
let count = 42 // number on stack
let active = true // boolean on stack
let ref = { id: 1 } // ref (address) on stack → object on heap
// Heap: the object `{ id: 1 }` lives here
ref.name = 'Kazi' // mutates heap objectfunction makeUser(name) {
return { name, createdAt: Date.now() } // new heap object each call
}
const a = makeUser('Alice')
const b = makeUser('Bob')
// `a` and `b` are different heap objects; stack holds two separate referencesInteractive: stack vs heap
Stack vs Heap Visualizer
Switch scenarios to see where primitives, references, and shared objects live in memory.
let count = 42
let active = true
let label = 'JS'
Stack
Fast, fixed-size frames. Freed when context pops.
Heap
No heap allocations — all values are primitives on the stack.
Objects and functions. Reclaimed by garbage collection when unreachable.
Primitive values are stored directly on the stack — copied by value.
Garbage collection
When no reference points to a heap object anymore, the engine's garbage collector reclaims that memory. Closures keep outer variables alive on the heap as long as the inner function is reachable.
function leakExample() {
const huge = new Array(1_000_000).fill('data')
return function () {
return huge.length // `huge` stays on heap while this function exists
}
}
const getter = leakExample()
// `huge` cannot be collected until `getter` is unreachableConclusion
JavaScript execution context is the bridge between the code you write and how the engine actually runs it. Every script starts in a global context; every function call spins up a new one; the call stack keeps synchronous execution ordered; and lexical environments power scope and closures.
Key takeaways:
- Execution context = variable environment + lexical environment +
thisbinding - GEC is created once at startup; FEC is created on every function call
- Creation phase hoists declarations; execution phase assigns values and runs logic
- The call stack is LIFO — push on call, pop on return
- Stack holds primitives and frames; heap holds objects — closures keep heap data alive
The next time hoisting, closures, or this confuse you, ask: which execution context am I in, and what does its lexical environment look like? That question usually leads straight to the answer.
Have questions about execution context or closures? Reach out via the contact page.
Subscribe to my newsletter
Stay up to date and get notified when I share new contents.
No spam ever, unsubscribe anytime