Execution contexts
Understand the global execution context, creation and execution phases, variable environments, and how globalThis and top-level this differ in scripts and modules.
- 01Describe the global contextExplain the first workspace JavaScript makes before your file runs.
- 02Tell setup from runningPredict what the creation phase sets up and what the execution phase changes.
- 03Compare scripts and modulesTell when a top-level name goes on
globalThis, and what top-levelthisis.
Every run starts with a workspace
Before your code does any work, JavaScript gets ready to run it. It makes a workspace for that code. This workspace is called an execution context. JavaScript makes one for the whole script, and one more for each function call.
An execution context keeps three things together:
- The names the code can use, like its variables and functions, with their values.
- An outer link: where to look next when a name isn't found here.
- The value of
thisfor the code that is running.
This lesson uses a simple model of contexts. The model tells you correctly what your code will do. Real engines store things in faster, more complex ways, and later lessons show the exact rules from the specification. You already know variables, local variables inside functions, closures, and strict mode. Execution contexts show how those ideas fit together while code runs.
In an office, every job gets its own desk. Before the work starts, someone puts three things on the desk. There is a notepad with the names the job needs. There is a note saying which room to ask if a name isn't on the notepad. And there is a name tag saying who the job is for. Then a person sits down and does the work, writing new values on the notepad as they go.
- In real life: A desk set up for one job
- In JavaScript: One execution context
- In real life: A notepad of names and values
- In JavaScript: The names the code can use, with their values
- In real life: A door to the room outside
- In JavaScript: The outer link: where to look next
- In real life: A name tag that says who the job is for
- In JavaScript: The value of
this
An execution context is the workspace JavaScript makes to run the whole script or one function call. It holds the code's names and values, an outer link, and a this value.
The global execution context
FIRST CONTEXTThe first context JavaScript makes is the global execution context. Code that is not inside any function is called top-level code, and it runs here. Functions written at the top level are also set up here.
When you call a function, JavaScript makes a new context for that call and puts it on top of the global one. When the function finishes, that context is removed. The next lesson, The call stack, looks closely at this adding and removing.
The global context comes with the global object. This is an object that holds things every script can use, like console and setTimeout. Browsers call it window. Browsers, Node.js, and every other place that runs JavaScript also call it globalThis. The global object is only part of the global context. In a normal script, a top-level let or const is a global variable, but it does not become a property of globalThis.
A company has one front office that everyone can find. Some names are on a public list at reception. Other notes stay private inside the office. When a visitor comes for a meeting, someone sets up a desk for that visitor. When the meeting ends, the desk is cleared.
- In real life: The building's front office
- In JavaScript: The global execution context
- In real life: The public list at reception
- In JavaScript: Properties of the global object, such as a top-level
varin a normal script - In real life: Private notes kept in the office
- In JavaScript: Top-level
letandconst - In real life: A short-term desk for one visitor
- In JavaScript: The context for one function call
function announce(label) { let message = label + " #" + count; console.log(message);} var status = "draft";let count = 1;count = count + 1;announce("Article");This code starts in the global context. Lines 1 to 3 write the function announce, but its body does not run yet. It runs only when line 9 calls the function. That call makes a new context, with its own parameter label and its own local variable message.
function greet(user) { const message = "Hi, " + user; return message;} console.log(greet("Asha"));Line 6 calls greet("Asha"). JavaScript makes a context for this call. In it, the parameter user holds "Asha". Line 2 makes a local variable, message, that joins the greeting and the name. The function returns it, so line 6 prints Hi, Asha.
greet("Asha"), JavaScript makes a new context for that call and puts it on top. The new context holds user and message. When greet returns, its context is removed.Creation phase vs execution phase
STEP THROUGHEvery context is used in two steps, called phases:
- The creation phase comes first. JavaScript reads the code and sets up its declarations. A declaration is a line that makes a name, like
var x,let x, orfunction f() {}. No line runs yet. - The execution phase comes next. JavaScript runs the lines one by one, from top to bottom, and changes values.
In the creation phase, each kind of declaration is set up in its own way:
- A function declaration is ready to call right away.
- A
varname is made and given the valueundefined. - A
letorconstname is made, but it has no value yet. Reading it before its line runs throws aReferenceError.
The table below uses the script from above. Read each row as: this name, made this way, holds this before any line runs, and this after the script finishes.
| Name | How is it declared? | What does it hold before any line runs? | What does it hold at the end? |
|---|---|---|---|
| announce | function | ready to call | function announce |
| status | var | undefined | "draft" |
| count | let | exists, can't be read yet | 2 |
console.log(greet()); function greet() { return "ready";}Line 1 calls greet, but the function is written below it, on line 3. This still works. The creation phase already made the function ready. Its body runs only because line 1 calls it, so the output is ready.
Now step through the script from above. The first step shows what the creation phase set up. The later steps are recorded from real JavaScript that runs the same code and prints the same output.
The first step shows what the creation phase set up. Then step through the execution phase, and watch the function call add a new context.
script
let message = label + " #" + count; console.log(message);} var status = "draft";let count = 1;count = count + 1;announce("Article");A later lesson, Hoisting & the temporal dead zone, goes deeper. For now, remember one rule: all declarations are set up before the code runs, but only some of them can be used before their own line.
var vs letconsole.log(ready);var ready = "yes"; console.log(count);let count = 1;Line 1 reads ready before line 2 gives it a value. The creation phase already made ready with undefined, so line 1 prints undefined. Line 4 reads count before line 5. The name count exists, but it has no value yet, so line 4 throws a ReferenceError. The time between the start of the code and the let line is called the temporal dead zone, or TDZ. During the TDZ, the name exists but can't be read.
var box starts with undefined in it, so an early read prints undefined. The let box stays locked until its own line runs, so an early read throws a ReferenceError.The lesson tests run this code as a real script. It prints undefined for the early var read. Then it throws a ReferenceError on the early let read.
- A function declaration becomes ready to call before its line runs
var countexists and holdsundefinedlet countexists but has no value yetcount = count + 1changes the stored valueconsole.log(message)prints- Calling a function adds a new context
Sort each event by when it happens. Use the step-through above if you're not sure.
Variable environments
LOOKUPA context keeps its names in lists. You will hear two words for these lists. In this lesson they mean:
- The variable environment is the list that
varnames go into. There is one for each function call, and one for the global context. - A lexical environment is a list for
let,const, and parameters. "Lexical" means "decided by where the code is written". Each block, like the inside of anif, can get its own lexical environment.
The specification has a more exact version of these lists, called Environment Records. A later lesson covers them.
Each list also has an outer link. It points to the list around the place where the function was written. When code uses a name, JavaScript looks in the current context first. If the name isn't there, JavaScript follows the outer link and looks there, and so on. The Closures lesson uses this same outer link: a function remembers the place it was written.
const room = "global";function outer() { const room = "outer"; inner();}function inner() { const task = "inspect"; console.log(room, task);}outer();Global execution context
The global context is made first. The functions outer and inner are ready to call. room exists, but it gets its value only when line 1 runs.
const room = "home";function showRoom() { const room = "kitchen"; console.log(room);} showRoom();console.log(room);Line 3 makes a second room, inside showRoom. When line 7 calls the function, line 4 finds the inner room first and prints kitchen. The inner name hides the outer one. This is called shadowing. Line 8 runs in the global context, so it prints the outer home. JavaScript follows the outer link only when the current context has no name that matches.
const place = "home"; function makeGreeting() { const user = "Asha"; function greeting() { return user + " at " + place; } return greeting();} console.log(makeGreeting());Line 6 is inside greeting, and it needs user and place. Neither is made inside greeting. So JavaScript follows the outer link to makeGreeting, where greeting was written, and finds user on line 4. It follows the next outer link to the global context and finds place on line 1. Line 11 prints Asha at home.
greeting() has no user or place of its own. It follows its outer link to makeGreeting(), where it was written, and finds user. The next outer link leads to the global context, where it finds place.The outer link depends on where a function is written, not on who calls it. Calling a function from inside another function does not change where it looks.
The table below sums up the three words. Read each row as: this word, what it means, which names go there, and where you will notice it.
| Word | What is it? | Which names go there? | Where do you notice it? |
|---|---|---|---|
| Variable environment | It is the list of names for one function call, or for the global context. | Names made with var go there. | You notice it in function scope and in global var names. |
| Lexical environment | It is a list of names tied to the place the code is written. | Names made with let and const, and parameters, go there. | You notice it in block scope, the TDZ, closures, and shadowing. |
| Outer link | It points to where JavaScript looks next. | No names go there. It is a link, not a list. | You notice it in nested functions and closures. |
globalThis & this at the top level
INTERACTIVEglobalThis is the standard name for the global object. In a browser, globalThis === window is true in normal code. But the value of this at the top level depends on how the file is loaded. There are two ways to load a file:
- A classic script is a normal
<script>tag. Its code is not in strict mode unless it asks for it. Code that is not in strict mode is sometimes called sloppy mode. - An ES module is a file loaded with
<script type="module">or withimport. A module is always in strict mode.
Here is what changes between them:
- In a classic script in sloppy mode, top-level
thisisglobalThis. - In an ES module, top-level
thisisundefined. - The top-level names in a module stay inside that module. They never go on
globalThis.
Inside a normal function, this works differently. It is decided by how the function is called, each time it is called. A function does not copy this from the code around it.
var becomes a property of globalThis, and top-level this is globalThis. In an ES module, a top-level var stays in the module, and top-level this is undefined.var scriptVar = "var property";function scriptFunction() { return "function property";}let scriptLet = "lexical only";const scriptConst = "lexical only"; console.log(this === globalThis);console.log(globalThis.scriptVar);console.log(globalThis.scriptFunction());console.log(globalThis.scriptLet);console.log(globalThis.scriptConst);thisglobalThis, in a classic script in sloppy modevar scriptVarmakes globalThis.scriptVarfunction scriptFunctionmakes globalThis.scriptFunctionlet / constglobal names, but not on globalThisA classic script is a normal <script> tag. Its top-level var and function declarations become properties of the global object. Its let and const names do not.
The table below compares four places. Read each row as: code running here has this this, and its top-level names go here.
| Where does the code run? | What is this? | Do its names go on globalThis? |
|---|---|---|
| In a classic script, sloppy mode | Top-level this is globalThis. | Top-level var and functions do. let and const do not. |
| In an ES module | Top-level this is undefined. | No. Every top-level name stays in the module. |
| In a plain function call, sloppy mode | this is globalThis. | No. A var inside the function stays in the function. |
| In a plain function call, strict mode | this is undefined. | No. A var inside the function stays in the function. |
const cart = { label: "tea", readLabel() { return this.label; },}; const read = cart.readLabel;console.log(cart.readLabel());console.log(read());Line 9 calls cart.readLabel(). The object before the dot, cart, becomes this, so it prints tea. Line 8 saved the same function into read, without its object. Line 10 calls read() with nothing before it. This code is in sloppy mode, so this becomes globalThis. There is no globalThis.label, so it prints undefined.
var scriptVar = "var property";function scriptFunction() { return "function property";}let scriptLet = "lexical only";const scriptConst = "lexical only"; console.log(this === globalThis);console.log(globalThis.scriptVar);console.log(globalThis.scriptFunction());console.log(globalThis.scriptLet);console.log(globalThis.scriptConst);In this classic script, line 8 prints true: top-level this is globalThis. Lines 9 and 10 find scriptVar and scriptFunction on globalThis. Lines 11 and 12 print undefined, because let and const names don't go there.
var moduleVar = "not a global property";let moduleLet = "module lexical";const moduleConst = "module lexical";function moduleFunction() { return "module function";} console.log(this === undefined);console.log(globalThis.moduleVar);console.log(globalThis.moduleFunction);console.log(typeof moduleLet);console.log(typeof moduleConst);In this module, line 8 prints true: top-level this is undefined. Lines 9 and 10 print undefined, because even var and function names stay in the module. Lines 11 and 12 print string, because the module itself can still use its own names.
Classic script: `var topic = "contexts";`Classic script: `function start() {}`Classic script: `let topic = "contexts";`Classic script: `const mode = "learn";`ES module: `var topic = "contexts";`ES module: `function start() {}`
Sort each declaration by what happens to it in a classic script or an ES module.
Where you'll use this
Execution contexts explain several bugs you will meet in real code:
- A
varprintsundefinedbefore the line that gives it a value. The creation phase made it withundefined. - A
letread throws before its line. That read is in the temporal dead zone. - A helper function reads or changes a global variable by accident. Now it depends on the global context, which every script on the page shares.
function scoreLabel(score) { var prefix = "Score"; return prefix + ": " + score;}console.log(scoreLabel(9));console.log(globalThis.prefix);Line 2 makes prefix inside the function, so it lives in the function's own context. Line 5 prints Score: 9. After the call, line 6 shows that globalThis.prefix is still undefined. This is safer than a global var, which every script on the page can read and change.
- First ask which context is running.
- Look for the name in that context. If it isn't there, follow the outer link.
- For
this, look at how the function is called: as a method, as a plain call, withnew, or as an arrow function. - To share a value between files, export it from a module or set a
globalThisproperty on purpose.
Common misconceptions
"The creation phase runs my function body."
No. The creation phase only makes a function declaration ready. The body runs when the function is called, during the execution phase.
"Hoisting moves declarations to the top of the file."
No. Your code stays in the order you wrote it. The creation phase sets up names before any line runs. That makes it look as if the declarations moved up. This effect is called hoisting.
"Top-level variables are always on globalThis."
Only some are. In a classic script, top-level var and functions go on globalThis, but let and const don't. In a module, no top-level name goes on globalThis.
"Top-level this is always the global object."
Only in a classic script in sloppy mode. In an ES module, top-level this is undefined.
"The step-through shows the engine's memory."
No. It replays values recorded from real JavaScript calls. It shows the values you need to learn the rules. It does not show how the engine really stores them in memory.
Practice: trace the contexts
5 EXERCISESvar readWhat does line 1 print?
console.log(topic);
var topic = "contexts";Line 1 prints undefined. The name topic exists because the creation phase made it. But line 2, which stores "contexts", has not run yet.
Which declaration in the starter code does not become a property of the global object?
var scriptVar = "var property";
let scriptLet = "lexical only";
console.log(globalThis.scriptVar);
console.log(globalThis.scriptLet);scriptLet does not become a property. globalThis.scriptVar is "var property", but globalThis.scriptLet is undefined.
What does inner() print?
let label = "global";
function outer() {
let label = "outer";
inner();
}
function inner() {
console.log(label);
}
outer();It prints global. outer has its own label, but inner is written at the top level. So when inner looks outward, it finds the global label.
varMove prefix inside the function, so it can't clash with a name from another script.
var prefix = "Score";
function scoreLabel(score) {
return prefix + ": " + score;
}function scoreLabel(score) {
const prefix = "Score";
return prefix + ": " + score;
}Now prefix is made inside the function's own context. The code no longer adds a global name or depends on one.
this in a moduleIn one word, what is top-level this inside an ES module?
Top-level this in an ES module is undefined. If you really need the global object, write globalThis. To share values between files, use import and export.
Quiz: check your understanding
7 QUESTIONSMake a guess before you choose. Every answer explains the rule it uses, so a wrong guess still teaches you something.
Question 1 of 7What is an execution context?
Choose an answer to see the explanation.
Question 2 of 7What does this classic script print before it throws?
Read the code, then predictconsole.log(topic); var topic = "contexts"; console.log(count); let count = 1;Choose an answer to see the explanation.
Question 3 of 7Which of these happens in the creation phase?
Choose an answer to see the explanation.
Question 4 of 7In a classic script, which top-level declarations become
globalThisproperties?Choose an answer to see the explanation.
Question 5 of 7What is top-level
thisin an ES module?Choose an answer to see the explanation.
Question 6 of 7What does this program print?
Read the code, then predictlet word = "global"; function say() { let word = "local"; console.log(word); } say(); console.log(word);Choose an answer to see the explanation.
Question 7 of 7Why does this lesson call its picture of contexts a model, and not the way engines really work?
Choose an answer to see the explanation.
Key takeaways
- An execution context is the workspace JavaScript makes to run the whole script or one function call.
- The global execution context is made first. Each function call makes one more context on top.
- The creation phase sets up declarations. The execution phase runs the lines and changes values.
- A
varname starts asundefined. Aletorconstname can't be read before its line: that time is the TDZ. - Scripts and modules differ. In a module, top-level
thisisundefined, and top-level names don't go onglobalThis.
Execution context, in one line.
An execution context is the workspace for running code: the names it can use, an outer link to look further out, and the value of this.
Up next: The call stack. You will see how JavaScript adds a context each time a function is called, and removes it when the function returns.