Lesson 1 · 12 min read

How JS Works: The Execution Context

Everything in JavaScript happens inside an execution context. How the global one is created — memory phase first, then code phase — and why that order explains almost everything that follows.

What you'll learn in this lesson

  1. Concept: everything happens inside an execution context
  2. Why this matters
  3. The two halves of an execution context
  4. The two phases: memory first, then code
  5. Traced example — step through it live
  6. The call stack: how contexts are managed
  7. Synchronous, single-threaded — what that actually means
  8. See it happen: Node and Chrome DevTools
  9. Predict the output
  10. Interview questions
  11. Practice before Lesson 2
  12. Conclusion

Concept

Everything in JavaScript happens inside an execution context. That one sentence is the whole lesson. When you run a file, the engine does not just start at line 1. It first builds a container — the execution context — and only then runs your code inside it. Every function you call gets its own container. Understanding what goes into that container, and in what order, is what makes hoisting, scope, closures and this easy to understand. (this is a keyword that points to an object. Lesson 5 explains it.)

Imagine the execution context as a box with two halves. The left half is a table of names and values. The right half is a pointer that walks through your code one line at a time. The engine always fills the left half completely before it starts walking the right half. That order is the source of almost every “why did JavaScript do that?” moment you will ever have. (The engine does not literally have a left and a right half. This is a picture to help you remember.)

Why this matters

You can write a lot of working JavaScript without knowing any of this. But the moment something surprises you — a variable that is undefined when you are sure you assigned it, a function that works when called “too early”, a counter inside a loop that prints the wrong number every time — the only real explanation is the execution context. Interviewers know this, which is why it is one of the most-asked fundamentals: it is a short question that shows whether you understand the language or only use it.

Every later lesson in this series builds directly on top of today: the call stack (Lesson 2) is a stack of execution contexts; hoisting (Lesson 3) is the result of the memory phase; scope (Lesson 7) is how one context can see the names of another; closures (Lesson 10) are a function keeping the variables of its context alive.

Key terms, explained simply

TermSimple meaning
JavaScript engineA JavaScript engine is a program that reads JavaScript code and runs it. Chrome and Node.js use an engine called V8.
VariableA variable is a named place in memory that holds a value. In var n = 2, n is the variable and 2 is its value.
FunctionA function is a named block of code that you can run again and again. square(4) runs the code inside square with the value 4.
Parameter and argumentA parameter is a name in the function's brackets, like num in square(num). An argument is the real value you pass when you call it, like 4 in square(4).
undefinedundefined is a special value that means “this variable exists, but nothing has been put in it yet.” (Lesson 6 explains it fully.)
Execution contextAn execution context is a container that the engine creates to run a piece of code. It has a memory half (names and values) and a code half (the line that is running now). Think of a workbench: the tools are laid out first, then the work starts.
Global execution context (GEC)The global execution context is the first context. The engine creates it when your file starts, and everything at the top level of the file lives in it.
Memory componentThe memory component is the half of a context that stores every variable and function as key → value pairs. A key → value pair is a name together with the value stored under it, like n → 2. Its other name is the variable environment.
Code componentThe code component is the half of a context that runs the code one line at a time. Its other name is the thread of execution.
Memory creation phasePhase 1. The engine reads through the code and sets aside memory for every variable (with the value undefined) and every function (with its whole body).
Code execution phasePhase 2. The engine runs the code line by line. It replaces the placeholders with real values and calls the functions.
HoistingHoisting is the effect of the memory creation phase: names look as if they were lifted to the top of their scope before the code runs, so you can use them early. Lesson 3 explains it fully.
ScopeScope is the part of the code where a name can be seen and used. Lesson 7 explains it fully.
ClosureA closure is a function that keeps access to the variables of the place where it was created, even after that place has finished running. Lesson 10 explains it fully.
StackA stack is a list where you can only add an item on the top and remove the item on the top. Think of a pile of plates. The last item added is the first one removed (Last In, First Out, or LIFO).
Call stackThe call stack is a stack that keeps track of which execution context is running. A new context is pushed (added) on top. A finished context is popped (removed) from the top. The context on top is the one that runs.
Function invocationA function invocation (also called a function call) means running a function by writing () after its name. Every invocation creates a brand-new execution context.
ThreadA thread is one path of work that a program follows, one step at a time. JavaScript has one main thread, so it is called single-threaded.
SynchronousSynchronous means one step after another: each step waits until the step before it has finished.
Difference in one line

An execution context is one box that holds the memory and the running code for one piece of code. The call stack is the pile that holds those boxes, so the engine knows which box is running now.

The two halves of an execution context

Here is the box. The left half is the memory component — the official name is variable environment. It stores every variable and function as a key → value pair. The right half is the code component — official name thread of execution. It executes your code one line at a time, top to bottom.

Execution context
Memory component
Variable environment
  • nundefined
  • square{ ...code }
  • square2undefined

Key → value pairs. Every variable and function gets a slot here before any code runs.

Code component
Thread of execution
  1. n = 2;
  2. function square … skip
  3. square2 = square(n);

One line at a time, top to bottom. One thread, one thing at a time — this is the “single-threaded” part.

An execution context is a box with two halves. The left half is filled first (memory creation phase), then the right half runs line by line (code execution phase). · one context
Both names matter

Interviewers use both sets of names. Memory component = variable environment. Code component = thread of execution. Use whichever you like, but know both.

The two phases: memory first, then code

When the engine creates an execution context, it works in two strictly ordered phases.

  1. Memory creation phase

    The engine reads through the code from top to bottom without running anything. For every var it finds, it reserves a slot in memory and puts the special placeholder undefined in it. (This is true for var. Lesson 3 shows that let and const are handled differently.) For every function declaration, it reserves a slot and stores the entire function body in it. When this phase ends, every name in your file already exists in memory.

  2. Code execution phase

    Now the engine goes back to line 1 and runs the code for real. Each assignment replaces the undefined placeholder with the actual value. Each function call creates a brand-new execution context (with its own two phases) and pushes it on the call stack. When the last line finishes, the context is removed.

  1. Run index.js
    engine starts
  2. creates
    Global execution context
    pushed onto the call stack
  3. phase 1
    Memory creation
    every var → undefined, every function → its code
  4. phase 2
    Code execution
    line by line; each function call = a new context
  5. last line done
    Context destroyed
    popped off the stack
What the engine does with one file, in order. · lifecycle

Traced example — step through it live

This is the program we will trace. It is deliberately tiny: one variable, one function, two calls. Read it once, guess what memory looks like after the memory phase, then step through the trace below and check your guess.

index.js
var n = 2;
function square(num) {
  var ans = num * num;
  return ans;
}
var square2 = square(n);
var square4 = square(4);
step 1 / 16
Setup
Program starts

Before a single line runs, the engine creates the Global Execution Context (GEC) and pushes it onto the call stack. It is empty for now.

index.js
1var n = 2;
2function square(num) {
3 var ans = num * num;
4 return ans;
5}
6var square2 = square(n);
7var square4 = square(4);
Call stack · top is running
Global execution contextrunning
memory: empty
Step through the program. Watch the memory phase fill every slot with undefined first, then the code phase overwrite them — and each function call get its own context on the stack. · interactive

Three things to notice in the trace:

  • After the memory phase, before any code runs, n, square2 and square4 are all undefined, but square already holds the full function. Nothing has “executed” yet.
  • Each call to square gets its own context with its own num and ans. In the second call, ans starts as undefined again. It remembers nothing from the first call.
  • return does two jobs: it hands a value back, and it hands control back to the line that made the call. The function's context is deleted the moment that happens.

The call stack: how contexts are managed

You just watched contexts pile up and disappear. The structure that manages them is the call stack. A stack is a list where you only add and remove at the top, like a pile of plates. The call stack follows one simple rule: the context on top is the one currently running. The global context is pushed first and sits at the bottom the whole time. Every function invocation pushes a new context on top; every return pops it. When the program finishes, the global context is popped and the stack is empty.

This is why the global context is only ever destroyed at the very end, and why a function called inside a function inside a function produces three contexts stacked up, popped off in reverse order. Lesson 2 explains this in detail. For today, just remember the picture.

Six names, one thing

Call stack, execution context stack, program stack, control stack, runtime stack, machine stack. Blog posts and interviewers use all of them. They all mean the same structure.

Synchronous, single-threaded — what that actually means

You will often hear “JavaScript is a synchronous, single-threaded language.” Now that you know the execution context, you can say exactly what it means:

  • Single-threaded — a thread is one path of work that a program follows, one step at a time. JavaScript has one call stack, so it has one thread of execution. The engine can only be inside one context at a time.
  • Synchronous — it means one step after another. Inside that thread, lines run in order. The engine does not move to the next line until the current one has finished. It never jumps ahead or runs two lines at once.
Then how does setTimeout work?

The engine itself is synchronous. Anything asynchronous (work that finishes later, such as timers, network calls and DOM events) is handled by the environment around the engine (the browser or Node). When the work is done, the event loop puts the result onto the call stack. The event loop is a mechanism that waits until the call stack is empty and then moves the next finished task onto it. Lesson 14 explains it. For now: the engine you are learning about today runs strictly one line at a time.

See it happen: Node and Chrome DevTools

Do not just trust the trace above. Watch the real engine do it. DevTools (short for developer tools) is the set of debugging panels built into Chrome. A debugger is a tool that lets you pause a running program and look inside it. A breakpoint is a marker on a line that tells the debugger to pause there. Save the example as index.js and use either method.

Method 1 — Chrome DevTools (recommended the first time)

  1. Create a page that loads the file

    index.html
    <!doctype html>
    <html>
      <body>
        <script src="index.js"></script>
      </body>
    </html>
  2. Open it and set a breakpoint on line 1

    Open index.html in Chrome, press F12, go to the Sources tab, click index.js, and click the line number 1 to add a breakpoint. Reload the page. Execution pauses before line 1 runs.

  3. Look at Scope while paused on line 1

    On the right, expand Scope → Global. You will see n: undefined, square2: undefined, square4: undefined and square: ƒ square(num). Nothing has executed and yet every name exists. That is the memory phase, live.

  4. Step and watch the Call Stack panel

    Press Step (F9) repeatedly. Watch n become 2. When you step into square(n), a new entry square appears in the Call Stack panel above (anonymous) (the global context), and Scope → Local shows num and ans. When return runs, the entry disappears.

Method 2 — Node with the inspector

Debug in Node
  • node --inspect-brk index.js

    Starts Node paused on the first line and opens a debugger port. The -brk is what makes it pause before line 1 — without it the script finishes before you can attach.

  • chrome://inspect

    Open this URL in Chrome and click inspect under your script. You get the same Sources / Scope / Call Stack panels as Method 1. (Node wraps your file in a function, so your names may be listed under Local instead of Global.)

Prefer staying in the terminal? Run node inspect index.js. It starts paused on the first line. Type n to step to the next line, repl to open a prompt where you can type a name to see its value, and c to continue.

What you should see

Paused on line 1, the global scope already contains every variable as undefined and the function in full. That single screenshot proves everything in this lesson.

Predict the output

Before running it, write down what each of the three console.log lines prints. Then reason through the memory phase and the code phase.

predict.js
console.log(a);
console.log(greet);
console.log(greet());

var a = 10;
function greet() {
  return "hello";
}
Show the answer
output
undefined
[Function: greet]
hello

Memory phase: a → undefined, greet → { …code }. Code phase, line 1: a is found in memory holding undefined, so that is printed. Line 2: greet holds the whole function, so the function is printed. Line 3: the function is invoked — a new context is created, it returns "hello", the context is popped, and hello is printed. Only then does line 5 assign 10 to a.

Interview questions

These are the questions this lesson lets you answer. Try to answer each one out loud before opening it.

What is an execution context?

A container (environment) that the engine creates to run JavaScript code. It has two parts: a memory component (variable environment) that stores variables and functions as key–value pairs, and a code component (thread of execution) that runs the code one line at a time. Every JS program starts by creating a global execution context, and every function call creates a new one.

What are the two phases of an execution context?

Memory creation phase: the engine scans the code and allocates memory for every variable (set to undefined) and function (the whole function body is stored). Code execution phase: the engine runs the code line by line, assigning real values and executing function calls.

Why does console.log(x) print undefined when x is declared with var later in the file?

Because of the memory creation phase. Before any code runs, x already exists in memory with the placeholder undefined. When the console.log runs in the code phase, it finds that placeholder. The real value is only assigned when execution reaches the x = ... line. (This is the mechanism behind hoisting, Lesson 3.)

Why can you call a function before it is defined?

During the memory phase, a function declaration is stored in full — not as undefined. So by the time the code phase reaches the call, the entire function body is already in memory.

What happens when a function is invoked?

A new execution context is created just for that call and pushed onto the call stack. It goes through its own memory phase (parameters and local variables reserved) and code phase. When the function returns, or finishes, its context is deleted and popped off the stack, and control goes back to the line that called it.

What is the call stack?

A stack (a list where you add and remove only at the top) that the engine uses to manage execution contexts. The global context sits at the bottom. Each function call pushes a new context on top; the one on top is always the one running. When it finishes it is popped. When the program ends, the global context is popped too and the stack is empty. Other names you will hear: execution context stack, program stack, control stack, runtime stack, machine stack.

JavaScript is “synchronous single-threaded”. What does that mean?

Single-threaded: there is one call stack, so only one command runs at a time. Synchronous: commands run in order, and the next line waits for the current one to finish. Async work (work that finishes later, such as timers and fetch) is handled outside the engine and comes back through the event loop — Lesson 14.

Does the same function called twice share memory between calls?

No. Each invocation creates a fresh execution context with its own memory. Local variables from the first call are gone by the time the second call runs. The only way to share is through an outer scope — which is what closures (Lesson 10) are about. A closure is a function that keeps access to the variables of the place where it was created.

Practice before Lesson 2

  1. Take the traced example and draw the execution contexts on paper — the global one and both square contexts — showing memory after phase 1 and after phase 2 for each. Compare against the stepper above.
  2. Run Method 1 in Chrome and take a screenshot of the Scope panel while paused on line 1. Look at it until it feels obvious that nothing has run yet.
  3. Change var square2 = square(n); to call a function that itself calls square. Predict how many contexts stack up, then confirm it in the Call Stack panel.
  4. Explain the memory phase and the code phase to someone in under one minute, without looking at this page. If nobody is there, say it out loud to yourself.

Conclusion

JavaScript does not run your file top to bottom. It first builds a global execution context, fills its memory with every variable (undefined) and every function (its full body), and only then runs the code one line at a time on a single thread. Every function call builds a brand-new context of its own, pushes it on the call stack, runs it, and throws it away on return. Remember this picture. The next thirteen lessons all add details to it.

Published by Jainik · Last updated · How these lessons are made

Keep learning

Part of the study guide

Related series