Execution contexts & jobs in the spec
Read how ECMAScript tracks running and suspended code with execution contexts, environment records, realms, generators, and host-scheduled jobs.
- 01Read a contextIdentify the running context, its realm, originating script or module, and its environment components.
- 02Track a pauseExplain why a generator or async function can suspend without losing its local state.
- 03Locate the host boundaryDistinguish ECMAScript Jobs and enqueue hooks from a browser or Node event loop.
A context tracks one evaluation
When JavaScript calls a function, it needs to know which code is running and where to find its names. The specification describes that state with an execution context: a device for tracking one evaluation. It is not an object your code can inspect.
function greet(user) {
return "Hi, " + user;
}
console.log(greet("Asha"));Line 1 declares greet. Line 2 builds a greeting from its parameter user. Line 4 calls it with "Asha" and prints Hi, Asha. For the duration of the call, the function's evaluation has its own context.
An execution context is a specification mechanism, not a JavaScript value or a required physical engine stack frame. Its job is to track evaluation, including where execution can continue after a pause. You cannot read the context through a property.
This lesson continues Environment Records. That lesson tells you where names live. Here we ask which context is using those records, when that context runs, and how later work starts. For a first look at ordinary calls, see the published call stack lesson.
The execution context stack
An execution context stack tracks contexts during evaluation. Its top entry is the running execution context. When control passes to code with a new context, the new context is pushed and becomes the running one. The caller waits.
function getPrice() {
return 3;
}
console.log(getPrice());Line 1 declares getPrice. Line 2 returns 3. Line 4 calls it, then prints 3. While the call runs, the new function context is on top; after it returns, the caller can finish line 4.
"The running execution context is always the top element of this stack."
"The newly created execution context is pushed onto the stack and becomes the running execution context."These quoted lines come from ECMA-262, Execution Contexts. They describe the specification's stack rule. The runnable example above shows the effect, but does not expose the contexts themselves.
function addTax(price) {
return price + 2;
}
function total() {
return addTax(3);
}
console.log(total());In the larger example, line 7 calls total. Line 5 calls addTax with 3. Line 2 adds 2. The printed result is 5. The guided replay below uses the real getTotal and addTax functions; it is an instrumented replay, not an engine debugger.
Replay of instrumented example functions, not an engine debugger.
script
function addTax(price) { return price + 2;}function total() { return addTax(3);}You stop writing on one notebook page to use another. When you finish, you return to the earlier page where you left off.
- In real life: One page is open
- In JavaScript: One context is running
- In real life: Open a new page
- In JavaScript: Call into a new context
- In real life: Keep the old page
- In JavaScript: The caller can resume
Where the analogy stops: A context is specification state, not a literal notebook page or necessarily a physical stack frame.
The simple call pattern is last-in, first-out: addTax finishes before total. Later sections show why a suspended generator need not stay on the stack while it waits. For a visual treatment of calls, visit the call stack lesson.
What a context remembers
A context needs more than a position in a stack. Its code evaluation state records what is needed to run, pause, and resume evaluation. Other components connect the current code with its function, realm, and script or module of origin.
function order(user) {
return user + " ordered tea";
}
console.log(order("Asha"));Line 1 defines order. Line 2 reads user and returns a string. Line 4 invokes the function and prints Asha ordered tea. In this call the Function component names order; in the surrounding script context it is null.
"code evaluation state" — state needed to perform, suspend, and resume evaluation
"Function" — the function object, or null for a Script or Module
"Realm" — the Realm Record used to access ECMAScript resources
"ScriptOrModule" — the originating Script Record or Module Record (or null)| Component | What the spec uses it for | What it does not mean |
|---|---|---|
| code evaluation state | Where evaluation can continue after a pause. | It is specification state, not a readable JavaScript property. |
| Function | The active function object, or null for script/module code. | Not a list of all functions waiting on the stack. |
| Realm | The realm whose intrinsic objects and global environment the code uses. | Not the filename or module resolver. |
| ScriptOrModule | The source Script Record or Module Record, or null when there is none. | Not the live call stack or an import URL string. |
| LexicalEnvironment | The Environment Record used to look up an identifier. | Not necessarily the same record as VariableEnvironment. |
| VariableEnvironment | The Environment Record for VariableStatement bindings. | Not every lexical block's current bindings. |
| PrivateEnvironment | The PrivateEnvironment Record for class private names, or null. | Not a private field's value on an instance. |
| Generator | The generator associated with a generator execution context. | Not present on every kind of context. |
The excerpt and table follow the current Execution Contexts definition. Function is null for script or module evaluation. Realm identifies the ECMAScript resources available to associated code; ScriptOrModule identifies its originating record or can be null. None of these components is a promise to expose an engine data structure.
LexicalEnvironment vs VariableEnvironment
LexicalEnvironment identifies the Environment Record used to resolve names in this context. VariableEnvironment identifies the record that holds bindings from var statements. Both are records, not two JavaScript objects created by every line.
function receipt() {
var price = 3;
if (true) {
let note = "tea";
console.log(note, price);
}
}
receipt();Line 2 sets price to 3 in the function. Line 3 enters a block. Line 4 makes the block-local note with value tea. Line 5 resolves both names and prints tea 3; line 8 calls the function. The current lexical record can change at a block while the function's var binding stays in its var scope.
"LexicalEnvironment" — resolves identifier references
"VariableEnvironment" — holds bindings made by VariableStatements
"PrivateEnvironment" — holds Private Names in the nearest containing class
"Generator" — the Generator this context evaluates, for generator contextsThe excerpt follows the spec's additional context state tables. PrivateEnvironment tracks private names declared by the nearest containing class; it is null when there is no containing class. The optional Generator component belongs to generator evaluations, not to every context.
var price = 3;
{
let price = 5;
console.log(price);
}
console.log(price);Line 1 sets the outer price to 3. Line 3 makes a different price in the block. Line 4 prints 5; line 6 prints 3. The block record points outward through [[OuterEnv]] for names it cannot find locally; it is not a new function call on the context stack.
| Part | Question it answers | Do not confuse it with |
|---|---|---|
| LexicalEnvironment | Resolve a read of note inside the block. | Can point to a new record when entering a block. |
| VariableEnvironment | Keep the var price binding in the function's var scope. | Does not move into the inner block for that var. |
| [[OuterEnv]] | Follow an outer record if the current record lacks a name. | Connects scopes, not consecutive calls on the stack. |
From ES3 variable objects to Environment Records
Older ECMAScript editions described a variable object as part of a context. That historical term is useful when reading old explanations, but it is not the current spec's general model. ECMA-262 now uses Environment Records with explicit operations and an outer link.
var price = 3;
let note = "tea";
console.log(price, note);Line 1 creates price with var. Line 2 creates note with let. Line 3 reads both and prints 3 tea. From JavaScript you read ordinary identifiers; you never get a variable-object API or an Environment Record to inspect.
The distinction matters when you encounter an old diagram claiming every scope is just one object. The current Environment Records definition says these are specification mechanisms that need not match an implementation artifact. Its declarative, object, global, function, and module forms explain cases a single simple object picture misses.
When reading today's spec, ask which Environment Record has the binding and what its [[OuterEnv]] points to. The earlier Environment Records lesson walks through those operations. This lesson focuses on which execution context holds the links while it runs.
This is a change of description, not a claim that an old browser physically stored every variable in one object or that modern engines must allocate a record for every block. The spec defines observable behavior; the implementation can optimize it.
Realms and ScriptOrModule
A realm provides intrinsic objects, a global object, and a global environment for code. The context's ScriptOrModule says which Script Record or Module Record its code came from. These are separate questions: which resources does the code use, and where did the code originate?
const cart = [];
console.log(Object.getPrototypeOf(cart) === Array.prototype);Line 1 creates an array. Line 2 compares its prototype to the current realm's Array.prototype and prints true. This snippet uses one realm; it does not create a second realm. The spec gives each realm its own intrinsic objects, so do not generalize this comparison across realms.
function receipt() {
return "tea";
}
console.log(receipt());Line 1 declares receipt. Line 2 returns tea. Line 4 prints tea. ScriptOrModule can point at the originating script record even while the function's call context runs; it is not a string named receipt.js or a request to schedule another task.
The Realms definition describes intrinsics and a global environment. The context table names ScriptOrModule. For a fuller cross-realm example, see Realms and globals. A realm belongs to an agent, which owns a context stack and running context; an agent is itself a spec mechanism, not necessarily a physical thread.
Jobs and host hooks
A Job is a no-argument abstract closure that starts ECMAScript computation when no other computation is in progress in its agent. The host is the environment running the language, such as a browser or Node. Hosts schedule jobs; ECMA-262 sets constraints without specifying one universal event loop.
console.log("start");
Promise.resolve("tea").then((order) => console.log(order));
console.log("end");Line 1 prints start. Line 2 registers a reaction for the fulfilled promise, but does not call it inline. Line 3 prints end. After that, the reaction prints tea. The output order is start, end, tea.
"A Job is an Abstract Closure with no parameters that initiates an ECMAScript computation when no other ECMAScript computation is currently in progress."
"Jobs must run in the same order as the HostEnqueuePromiseJob invocations that scheduled them."The short quotes are from Jobs and Host Operations. HostEnqueuePromiseJob schedules Promise-related jobs and must preserve the order of its enqueue calls. HostEnqueueGenericJob schedules a generic job without those added priority and order constraints.
Promise.resolve().then(() => console.log("first"));
Promise.resolve().then(() => console.log("second"));
console.log("now");Lines 1 and 2 queue reactions in that order. Line 3 prints now first; then the reactions print first and second. The test runs the real Promise code asynchronously; a script cannot inspect the host's job queue directly.
| Term | Role | One useful distinction |
|---|---|---|
| Execution context | State while code runs or is suspended. | Tracks one evaluation, not a work queue. |
| Execution context stack | The contexts currently stacked for an agent. | Top is the running context. |
| Job | A no-argument abstract closure that starts computation later. | A host schedules it when no other computation runs. |
| HostEnqueuePromiseJob | Host hook for Promise-related jobs with ordering constraints. | Promise jobs scheduled through it run in enqueue order. |
| HostEnqueueGenericJob | Host hook for a job with no extra priority or order constraint. | Does not specify a browser task queue. |
console.log("script");
setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));Line 1 logs script. Line 2 requests a host timer. Line 3 registers a Promise reaction. In a browser the timer is not a Promise job; the browser's event loop controls when its callback runs. ECMA-262's job definition alone does not promise a universal timer order across every host.
To study that scheduling in detail, follow the published event loop and microtasks lessons. The spec here tells us why a reaction is later work and why jobs in one agent do not overlap; HTML and Node provide their host rules.
Generator execution contexts can pause
A generator returns an iterator whose next() method resumes its body. At yield, its evaluation is suspended and the caller runs again. The saved state is available for a later next(). That is different from leaving an ordinary nested call running forever.
function* orders() {
yield "tea";
return "done";
}
const order = orders();
console.log(order.next().value);
console.log(order.next().value);Line 1 defines the generator. Line 2 yields tea. Line 5 creates its iterator without running the body. Line 6 resumes it and prints tea. Line 7 resumes after the yield, returns, and prints done. The context can leave the running stack at yield while its state remains saved.
"Set gen.[[GeneratorState]] to SUSPENDED-YIELD."
"Return ? RunCallerContext(iteratorResult)."
"Return ? RunSuspendedContext(genContext, NormalCompletion(value))."These short steps paraphrase notation only where shown; see GeneratorYield, GeneratorResume, and RunSuspendedContext for the full algorithms. The context's Generator component connects it with the generator object; its evaluation state says where to continue.
function* countOrders() { let count = 1; yield count; count += 1; yield count;}const orders = countOrders();console.log(orders.next().value);console.log(orders.next().value);not startedBody has not run.
noneThe third call ends the generator without another log.
The generator object exists, but its body has not run. Press Next call.
Replay of instrumented generator next calls, not an engine debugger.
script
function* countOrders() { let count = 1; yield count; count += 1; yield count;}console.log(orders.next().value);console.log(orders.next().value);Press Next call once to see 1, again to see 2, then again to finish. The displayed state is a teaching model, not an engine inspection. Its source is runnable and a test runs all three calls. Reset restores the state before the first next() call.
async function receipt() {
console.log("before");
await Promise.resolve();
console.log("after");
}
receipt();
console.log("outside");Line 2 logs before; line 3 awaits even a fulfilled promise. Line 6 calls receipt and line 7 logs outside. After the Promise reaction resumes the async evaluation, line 4 logs after. The output is before, outside, after. This is a resumption, not a second call to the function body from its first line.
Await captures the async context and arranges its resumption through Promise reactions. For the full language-level walkthrough, visit the published async/await lesson. The same stack model cannot explain a context held while another computation runs unless it includes suspension.
Use the model in an app
Suppose a checkout page calculates a total, updates a message after a Promise, and then receives a click. These are not all one uninterrupted call. A synchronous function call uses the current stack; a Promise reaction starts later as a job; the browser supplies the click and timer scheduling rules.
function showTotal(price) {
return price + 2;
}
console.log(showTotal(3));Line 1 declares showTotal. Line 2 adds 2 to the price. Line 4 calls it with 3 and prints 5. That is an ordinary nested call: the function's context runs on top, then the script continues.
const order = { item: "tea" };
console.log(order.item);Line 1 stores an order object. Line 2 prints tea. The code does not create an agent. An agent is the spec's owner of a context stack and running context, among other state. The Agents definition allows separate agents to make progress independently; it does not say each is a dedicated hardware thread.
For a real site, use this model to identify which name belongs to which scope, where a function pauses, and which transition belongs to the host. When debugging order, log at boundaries rather than assuming every callback remains on the original synchronous stack. Microtasks covers the browser behavior; Node's event loop covers its different host.
- Where the running code resolves
note - The function currently being evaluated
- The originating Script Record or Module Record
- Where a Promise reaction job is enqueued
- Scheduling an unconstrained generic job
- Which Array.prototype belongs to the code
Place each spec clue where it belongs, then compare the explanation.
Common mix-ups
A stack, a job, a realm, and an environment answer different questions. Keeping them separate makes spec algorithms less mysterious. Start with one small call before deciding which part an unfamiliar step describes.
function getPrice() {
return 3;
}
console.log(getPrice());Line 1 defines getPrice. Line 2 returns 3. Line 4 prints 3 after a synchronous call. No Promise handler or queued job is required for that ordinary call.
- "Every context is one physical stack frame." A context is a spec device and can suspend.
- "A generator stays running while paused." Its context can leave the stack and later resume.
- "A Realm is a job queue." It provides intrinsics and a global environment.
- "The spec defines the browser's event loop." It defines jobs and hooks; the host supplies scheduling.
- "VariableEnvironment always means one object with every variable." Both environment components name records.
| Name | What it is | Not the same as |
|---|---|---|
| A context | Specification state for evaluating code; it may be suspended. | A visible JavaScript object or exactly one native stack frame. |
| A realm | A set of intrinsics and a global environment for code. | The script file, the job queue, or necessarily an agent. |
| A Promise job | Specified work sent to a host hook with ordering rules. | A timer callback or a universal event-loop algorithm. |
| A suspended generator | Retains state until resumed or completed. | An actively running call on the stack the whole time. |
If two descriptions seem contradictory, check whether one talks about an active synchronous call and the other about a suspended evaluation. The usual nested-call stack behavior is correct, but it is not the entire story once generators and await are involved.
Practice exercises
Start by predicting an ordinary call. Then track two bindings, a Promise reaction, and a suspended context. Use the answer checker for the short response and compare the worked solution with the code you ran.
Read the last line first: it calls total. Trace the inner call before you write the printed number. Which context is running while the addition happens?
function addTax(price) {
return price + 2;
}
function total() {
return addTax(3);
}
console.log(total());console.log(3 + 2); // 5The call stack runs addTax before total can return. The last line prints 5.
The block declares a new price. Write the two printed numbers in order. Which environment is consulted first for the read inside the block?
var price = 3;
{
let price = 5;
console.log(price);
}
console.log(price);console.log(5);
console.log(3);The block prints its own price, then the outer code prints its var price.
A customer orders tea, and a Promise handler will print the item. Write the three console lines in order. The Promise is already fulfilled, but the handler still waits.
console.log("start");
Promise.resolve("tea").then((order) => console.log(order));
console.log("end");console.log("start");
console.log("end");
console.log("tea");The output is start, end, tea. This shows a later reaction job, not a timer.
The first next() yields tea and pauses the generator. On the second next(), where does evaluation continue? Describe the place in a few words.
function* orders() {
yield "tea";
return "done";
}
const order = orders();
console.log(order.next().value);
console.log(order.next().value);function* orders() {
yield "tea";
return "done";
}
const order = orders();
order.next(); // yields tea
console.log(order.next().value); // doneThe saved generator context resumes after yield. It does not start over.
Your checkout page shows a price plus a small fee. While the addition runs, what function owns the running context? Name it before changing any app code.
function showTotal(price) {
return price + 2;
}
console.log(showTotal(3));function showTotal(price) {
return price + 2;
}
console.log(showTotal(3)); // 5The showTotal call is running while price + 2 is evaluated. The script resumes after it returns.
Imagine this as a receipt update after checkout. Predict the three logs, then identify which work occurs after the function pauses at await.
async function receipt() {
console.log("before");
await Promise.resolve();
console.log("after");
}
receipt();
console.log("outside");console.log("before");
console.log("outside");
console.log("after");The async context pauses at await; the script prints outside; the Promise reaction resumes the async work.
Check your understanding
Trace a call before thinking about scheduling. For each question, decide whether you need the running context, an environment record, a realm, or a later job.
Question 1 of 7When total calls addTax, which execution context runs?
Choose an answer to see the explanation.
Question 2 of 7What does this nested call print?
Read the code, then predictfunction addTax(price) { return price + 2; } console.log(addTax(3));Choose an answer to see the explanation.
Question 3 of 7Which component resolves the name note inside a block?
Choose an answer to see the explanation.
Question 4 of 7What does this shadowed binding print?
Read the code, then predictvar price = 3; { let price = 5; console.log(price); } console.log(price);Choose an answer to see the explanation.
Question 5 of 7Who chooses the browser or Node event loop details?
Choose an answer to see the explanation.
Question 6 of 7What does this Promise program print?
Read the code, then predictconsole.log("start"); Promise.resolve("tea").then((item) => console.log(item)); console.log("end");Choose an answer to see the explanation.
Question 7 of 7What happens when a generator reaches yield?
Choose an answer to see the explanation.
When a code question surprises you, run the displayed snippet and then explain each output with the relevant context transition. The quiz is about spec vocabulary, not engine timings or a guessed implementation detail.
Key takeaways
The specification keeps three questions apart: what is running now, what state is saved for later, and who schedules later work. Ordinary calls and resumable code use the same vocabulary without behaving identically.
- The top of an agent's execution context stack is its running context.
- Code evaluation state can be saved and resumed. A context is a spec device, not an exposed object.
- LexicalEnvironment resolves names; VariableEnvironment tracks var bindings; PrivateEnvironment tracks class private names.
- Function, Realm, ScriptOrModule, and optional Generator answer different questions about an evaluation.
- Promise jobs use HostEnqueuePromiseJob; the host supplies the event loop while obeying spec constraints.
- Generators yield and resume. Await also suspends evaluation before a Promise reaction resumes it.
Remember the one-liner.
A context tracks evaluation; a job starts later computation; the host schedules it.
Coming next: Function calls: [[Call]] & [[Construct]]. You will see how a function invocation creates and prepares the context we have been tracking. Revisit Environment Records for the binding side of this story.