The Hidden Engine Behind Your Code: Execution Context in JavaScript
Execution context in JavaScript is the environment where code runs, consisting of a creation phase for setting up memory and an execution phase for running code, with each function call creating its own isolated context connected through the scope chain and managed by the call stack.
At first, JavaScript feels like it runs code line by line.
Top to bottom.
Simple. Predictable.
But then you encounter things like:
- variables existing before they’re assigned
- functions working before they’re defined
- values being found “outside” of where you wrote them
And you start to realize:
Something else is happening behind the scenes.
That “something” is execution context.
Code Doesn’t Run in a Vacuum
When JavaScript runs your code, it doesn’t just execute it directly.
It first creates an environment.
A place to store:
- variables
- functions
- scope relationships
- the value of
this
This environment is called an execution context.
You can think of it as a workspace.
Before doing any work, JavaScript sets up the table.
Then it starts working.
Every Program Starts With a Global Context
When your script begins, JavaScript creates a global execution context.
This is the first and main workspace.
const a = 10
function foo() {}Before execution, JavaScript prepares:
afoo
Even though they appear later in the code.
This preparation phase is why hoisting exists.
Two Phases: Setup First, Then Execution
Every execution context goes through two phases.
First, the creation phase.
JavaScript scans the code and prepares memory.
Variables are registered.
Functions are stored.
Scope is defined.
Then comes the execution phase.
Now the code actually runs.
Values are assigned.
Functions are called.
Expressions are evaluated.
Understanding this split explains a lot of “weird” behavior.
Why Function Calls Create New Contexts
Now consider what happens when a function runs.
const a = 10
function foo() {
const b = 20
console.log(a + b)
}
foo()When foo() is called, JavaScript creates a new execution context.
A new workspace.
Why?
Because the function needs its own environment:
- its own variables (
b) - its own scope
- its own execution state
If JavaScript reused the global one, everything would mix together.
Variables would collide.
Behavior would become unpredictable.
So instead, each function call gets its own isolated context.
These Contexts Are Connected, Not Nested
It’s tempting to imagine contexts like nested boxes.
But that’s not quite accurate.
They are separate, but linked.
When foo runs:
- it has its own context
- but it also knows about the global context
So when JavaScript encounters:
console.log(a + b)It looks for variables like this:
- check inside
foo→ findsb - check outside → finds
a
This lookup process is the scope chain.
Why Variable Shadowing Happens
Now consider this:
let a = 1
function foo() {
let a = 2
console.log(a)
}
foo()This prints 2.
Because each execution context has its own variables.
When JavaScript looks for a:
- it finds
a = 2in the current context - it stops searching
The outer a is still there.
But it’s hidden.
This is called variable shadowing.
The Role of the Call Stack
Execution contexts don’t exist randomly.
They are managed in a structure called the call stack.
When your program starts:
Global ContextWhen a function is called:
Global
→ foo()If another function is called inside:
Global
→ foo()
→ bar()Each new call adds a new execution context to the stack.
When a function finishes, its context is removed.
This is how JavaScript keeps track of what it’s currently doing.
How Everything Connects
Execution context is not an isolated concept.
It explains many other parts of JavaScript:
- hoisting → happens during the creation phase
- lexical scope → determined by context relationships
- closures → functions remembering their context
this→ defined within the execution context
Once you see this, these concepts stop feeling separate.
They become parts of the same system.
A Better Way to Think About It
Instead of imagining JavaScript as “running code”…
think of it as:
first preparing an environment
then executing within that environment
Every time a function runs, a new environment is created.
Each one is isolated.
But connected to where it came from.
A Final Thought
Execution context is invisible.
You don’t see it in your code.
But it’s always there.
Every variable you use…
every function you call…
every value you access…
all of it depends on this hidden system.
And once you see it clearly…
JavaScript stops feeling unpredictable.
Because now you can trace what’s really happening behind the scenes.