What Actually Happens When JavaScript Runs? From Source Code to Execution
You write this:
const price = 100;
const taxRate = 0.15;
function calculateTotal(price) {
return price + price * taxRate;
}
console.log(calculateTotal(price));
Then you run it.
A moment later:
115
appears.
It feels as though JavaScript simply reads the file from top to bottom and executes each line.
That mental model is useful when learning the language.
It is also incomplete.
Before your code can produce 115, a modern JavaScript engine may need to scan the source, parse its grammar, build internal representations, create execution environments, generate bytecode, collect runtime feedback, compile frequently executed code into machine instructions, optimize those instructions, and potentially throw those optimizations away later.
JavaScript execution is therefore not simply:
Source Code
↓
Execute
A more useful mental model is:
Source Code
↓
Scanning / Parsing
↓
Internal Representation
↓
Execution Environment
↓
Bytecode / Machine Code
↓
Execution
↓
Runtime Feedback
↓
Optimization
And even this is a simplification.
Let's follow the process from the beginning.
The Short Answer
When JavaScript runs, the JavaScript engine first has to understand the source code before it can execute its meaning.
Broadly, the process looks like this:
The runtime receives JavaScript source text.
The engine scans and parses that text.
Syntax and certain early errors are detected.
The engine creates internal representations of the program.
JavaScript execution contexts and lexical environments are established according to ECMAScript semantics.
The engine begins executing the program.
Modern engines may initially execute an intermediate representation such as bytecode.
Frequently executed code can be compiled into machine code.
Runtime information is used to optimize hot code.
If the assumptions behind an optimization become invalid, the engine can deoptimize the code.
Memory that is no longer reachable is eventually reclaimed by the garbage collector.
The ECMAScript specification defines what JavaScript programs mean, including concepts such as execution contexts and lexical environments. It does not require every JavaScript engine to use the same parser, bytecode format, compiler pipeline, or optimization strategy.
That distinction is essential throughout this article.
JavaScript Is a Language, Not a Runtime
One of the first misconceptions to remove is this:
JavaScript is not Chrome, Node.js, V8, or the browser event loop.
JavaScript is standardized as ECMAScript.
The ECMAScript specification defines the behavior of things such as:
const x = 10;
function add(a, b) {
return a + b;
}
It describes how declarations behave, how functions are called, how scopes work, how objects behave, and how execution contexts operate.
But ECMAScript is intentionally not a complete application environment. Facilities such as networking, user interfaces, filesystem access, and many forms of external I/O are supplied by the host environment rather than by the core language specification.
That gives us three different concepts:
JavaScript / ECMAScript
↓
Language semantics
JavaScript Engine
↓
Executes ECMAScript
Host Runtime
↓
Provides APIs and environment
Examples include:
Browser
├── JavaScript engine
├── DOM
├── timers
├── fetch
├── events
└── rendering engine
and:
Node.js
├── JavaScript engine
├── filesystem APIs
├── networking APIs
├── timers
└── runtime infrastructure
V8, for example, is the JavaScript and WebAssembly engine used by Chrome and Node.js.
Step 1: The Engine Receives Source Code
Everything starts as source text.
Consider:
function multiply(a, b) {
return a * b;
}
multiply(4, 5);
To us, this contains obvious concepts:
a function
parameters
a return statement
multiplication
a function call
To the engine, the input initially arrives as characters that must be understood according to JavaScript's grammar.
The engine cannot simply send:
return a * b;
directly to the CPU.
A CPU executes machine instructions, not JavaScript syntax.
Something must bridge that gap.
Step 2: Scanning Turns Source Text Into Meaningful Pieces
The first stages of processing identify meaningful lexical elements in the source.
Consider:
const answer = 40 + 2;
Conceptually, this can be broken into pieces such as:
const
answer
=
40
+
2
;
These pieces are commonly called tokens.
A scanner or lexer recognizes things such as:
keywords
identifiers
numeric literals
string literals
operators
punctuation
V8 has a dedicated scanner as part of its parsing pipeline, and parser performance is important enough that the V8 team has specifically optimized scanning behavior.
But a stream of tokens still does not tell the engine the complete structure of the program.
For that, it needs parsing.
Step 3: Parsing Determines the Structure of the Program
Now consider:
const answer = 40 + 2;
The engine needs to understand that this means something approximately like:
Variable Declaration
├── Identifier: answer
└── Initializer
└── Addition Expression
├── Number: 40
└── Number: 2
This structural representation is commonly expressed using an Abstract Syntax Tree, or AST.
Conceptually:
Program
└── VariableDeclaration
└── VariableDeclarator
├── Identifier
│ └── answer
└── BinaryExpression
├── 40
├── +
└── 2
The exact internal representation differs between engines.
For V8 specifically, the parser can produce an AST that later feeds the bytecode-generation pipeline.
Syntax Errors Can Stop Us Here
Consider:
const user = ;
There is no valid expression after =.
The source does not satisfy JavaScript's grammar.
The engine cannot meaningfully proceed with normal execution.
You get a syntax error.
This distinction becomes useful:
Syntax problem
→ JavaScript cannot correctly construct the program
Runtime problem
→ JavaScript understood the program,
but something failed while executing it
For example:
console.log(user.name);
may be perfectly valid JavaScript syntax.
But if:
user === undefined
the expression can fail during execution.
The parser can understand the program without knowing every runtime value it will encounter.
Modern Engines Don't Necessarily Fully Parse Everything Immediately
Parsing has a cost.
Imagine downloading several megabytes of JavaScript while visiting a large application.
Some functions might never execute during that session.
Fully processing every function immediately would waste work.
Modern browsers therefore use techniques such as lazy parsing or preparsing.
V8, for example, can preparse functions instead of immediately constructing a complete AST and compiling bytecode for every function encountered. When the function is actually needed, more work can then be performed.
Conceptually:
function rarelyUsedFeature() {
// 500 lines of code
}
If this function never executes, the engine may be able to avoid some of the expensive work associated with fully preparing it upfront.
This is an important recurring theme in runtime engineering:
Do expensive work when it becomes necessary, not simply because it might become necessary.
Step 4: JavaScript Prepares the Execution Environment
Parsing explains the structure of the code.
It does not yet explain things such as:
console.log(name);
var name = "Geljek";
or:
console.log(name);
let name = "Geljek";
These behave differently.
To understand why, we need to leave compiler implementation details for a moment and look at the ECMAScript execution model.
The specification defines execution contexts and maintains an execution-context stack. A running execution context represents the context in which execution is currently taking place.
A simplified mental model is:
Execution Context
├── current code
├── lexical environment
├── variable environment
├── function information
└── other execution state
Do not treat this diagram as an exact engine memory layout.
It is a mental model for language semantics.
The Global Execution Context
When a script begins executing, there is an environment associated with that execution.
Consider:
const site = "Geljek";
function greet() {
return `Welcome to ${site}`;
}
greet();
The global-level code establishes bindings for:
site
greet
When greet() runs, another function execution context becomes active.
Conceptually:
Global Context
↓
greet() Context
When greet() finishes:
greet() Context removed
↓
Global Context continues
Function Calls Create New Execution Contexts
Consider:
function first() {
second();
}
function second() {
third();
}
function third() {
console.log("Done");
}
first();
Conceptually:
Global
Then:
Global
first()
Then:
Global
first()
second()
Then:
Global
first()
second()
third()
The most recently entered active execution context runs first.
After third() returns:
Global
first()
second()
Then:
Global
first()
And finally:
Global
This naturally leads to one of the best-known JavaScript concepts:
The Call Stack
The call stack is a useful model for tracking active function calls.
For example:
function calculate() {
return multiply(5, 10);
}
function multiply(a, b) {
return a * b;
}
calculate();
At the deepest point:
┌──────────────────┐
│ multiply(5, 10) │
├──────────────────┤
│ calculate() │
├──────────────────┤
│ global code │
└──────────────────┘
When multiply() returns:
┌──────────────────┐
│ calculate() │
├──────────────────┤
│ global code │
└──────────────────┘
Eventually only global execution remains.
What About "Hoisting"?
JavaScript developers are often taught:
JavaScript moves declarations to the top.
That is a convenient beginner explanation.
JavaScript does not literally rewrite this:
console.log(name);
var name = "Abdullah";
into:
var name;
console.log(name);
name = "Abdullah";
Instead, declaration processing establishes bindings before their corresponding statements execute.
The exact behavior depends on the declaration type.
For example:
console.log(name);
var name = "Geljek";
prints:
undefined
But:
console.log(name);
let name = "Geljek";
throws an error.
let and const bindings are created as part of environment setup, but they cannot be accessed normally until evaluation reaches their declaration. The specification describes this behavior through lexical environments and declaration instantiation rather than by physically moving source code.
This inaccessible interval is what developers commonly call the Temporal Dead Zone.
So a better mental model than "JavaScript moves variables upward" is:
JavaScript establishes bindings before executing statements, but different declaration types have different initialization rules.
Step 5: The Engine Needs Something Executable
We now have:
Source
↓
Tokens
↓
Parsed structure / AST
But CPUs still cannot execute an AST directly.
Modern engines therefore transform JavaScript into executable internal forms.
This is where the old question—
Is JavaScript interpreted or compiled?
—becomes misleading.
Modern JavaScript engines use both interpretation and compilation techniques.
The exact architecture varies between engines and evolves over time.
Let's use V8 as a concrete example.
V8's Execution Pipeline
Modern V8 uses multiple execution tiers rather than one simple compiler.
At a simplified level:
JavaScript Source
↓
Parser
↓
AST
↓
Ignition Bytecode
↓
Execution + Runtime Feedback
↓
Faster compilation tiers
↓
Optimized Machine Code
V8's Ignition component produces bytecode from parsed JavaScript, which the Ignition interpreter can execute.
But current V8 goes beyond the older "Ignition + TurboFan" explanation that appears in many JavaScript tutorials.
Its execution pipeline has evolved to include multiple compilation tiers, including Sparkplug, Maglev, and TurboFan for different performance/compilation-cost tradeoffs.
A better simplified picture is therefore:
JavaScript
↓
Parser
↓
Ignition Bytecode
↓
Ignition
Interpreter
│
Runtime Feedback
│
┌──────────┼──────────┐
↓ ↓ ↓
Sparkplug Maglev TurboFan
baseline optimizing high-tier
compiler compiler optimizer
This is still simplified, but it is much closer to how a modern tiered JavaScript engine should be imagined.
What Is Bytecode?
Machine code is specific to a CPU architecture.
For example, x86-64 and ARM processors have different instruction sets.
Bytecode is an intermediate representation used by the engine.
Suppose we have:
function add(a, b) {
return a + b;
}
Conceptually, bytecode might represent operations resembling:
Load argument a
Load argument b
Add
Return
That is not actual V8 bytecode syntax; it is simply the idea.
The important relationship is:
JavaScript Source
↓
Intermediate Instructions
↓
Engine Executes Them
V8's Ignition interpreter executes bytecode, and that bytecode can also provide input to later compilation stages.
Why Not Compile Everything to Highly Optimized Machine Code Immediately?
Because optimization is expensive.
Consider a function:
function add(a, b) {
return a + b;
}
It may run:
1 time
or:
1,000,000,000 times
Spending significant CPU time aggressively optimizing a function that executes once can cost more than the optimization saves.
This creates a fundamental runtime tradeoff:
Compile Quickly
vs
Execute Quickly
A fast compiler can start code sooner.
A sophisticated optimizing compiler can produce faster code but spends more time analyzing and compiling it.
Tiered execution tries to get both benefits.
Start cheaply.
Then optimize code that proves important.
V8's different compilation tiers exist specifically to balance compilation speed against peak execution performance.
Hot Code
Code that executes frequently is often called hot code.
Consider:
function square(number) {
return number * number;
}
for (let i = 0; i < 10_000_000; i++) {
square(i);
}
square() runs millions of times.
That makes optimizing it potentially worthwhile.
The engine can gather information while executing it.
For example, it may observe that number repeatedly contains numeric values.
This runtime information can help later compilation stages make stronger assumptions and generate faster instructions.
V8 explicitly collects runtime feedback such as information about object shapes and encountered types while JavaScript executes.
JavaScript's Dynamic Nature Makes Optimization Interesting
Consider:
function add(a, b) {
return a + b;
}
add(10, 20);
add(100, 200);
add(5, 7);
So far:
number + number
Then someone calls:
add("Gel", "jek");
Now:
string + string
JavaScript allows this.
The engine cannot always assume from source code alone that a and b will forever be numbers.
But after observing many calls such as:
add(1, 2);
add(3, 4);
add(5, 6);
an optimizing compiler may generate specialized code based on observed runtime behavior.
Conceptually:
Observation:
a repeatedly looks like Number
b repeatedly looks like Number
Optimization:
Generate a fast path for numbers
That can be much faster than handling every theoretical JavaScript possibility on every operation.
Optimization Is Speculation
This gives us one of the most important concepts in modern JavaScript engines:
Optimized JavaScript execution is often speculative.
The engine observes:
"This function seems to receive numbers."
Then it creates optimized code based on that assumption.
But JavaScript remains dynamic.
The assumption may later become false.
Consider:
function double(value) {
return value + value;
}
for (let i = 0; i < 100000; i++) {
double(i);
}
double("Geljek");
For a long time:
number → number
Then suddenly:
string → string
The engine must still preserve correct JavaScript semantics.
Performance optimization is never allowed to change what the language means.
Deoptimization
Suppose optimized code was generated under an assumption that stops being valid.
The engine may need to abandon that optimized path.
This process is commonly called deoptimization.
Conceptually:
General execution
↓
Observe behavior
↓
Optimize
↓
Assumption fails
↓
Deoptimize
↓
Return to safer execution
V8's adaptive optimization system explicitly supports deoptimization, allowing optimized execution to fall back when speculation is no longer valid.
This is why performance discussions such as:
JavaScript is dynamically typed, therefore it cannot be optimized.
are far too simplistic.
Dynamic behavior makes optimization harder.
It does not make optimization impossible.
Objects Matter Too
Consider:
const user1 = {
name: "Amina",
age: 24,
};
const user2 = {
name: "Rahim",
age: 29,
};
These objects have the same structural shape:
name
age
Now compare:
const user3 = {
username: "karim",
score: 100,
verified: true,
};
This has a different structure.
Modern engines can use information about object shapes as part of their optimization machinery, and V8 tracks object-shape information during execution for optimization purposes.
That means JavaScript performance is influenced not only by individual lines of code but also by patterns of runtime behavior.
Again, this does not mean you should start contorting normal application code around obscure engine tricks.
Readable, maintainable algorithms should come first.
Understanding engine behavior is useful primarily because it gives us a better mental model of what "dynamic language" actually means internally.
What Happens During a Function Call?
Let's return to a simple program:
const multiplier = 2;
function multiply(value) {
const result = value * multiplier;
return result;
}
const answer = multiply(21);
console.log(answer);
Conceptually, execution proceeds like this.
1. Global environment is prepared
Bindings are established for:
multiplier
multiply
answer
according to their declaration semantics.
2. Global statements execute
const multiplier = 2;
is evaluated.
3. multiply(21) is reached
A new function execution context is established.
Conceptually:
multiply Execution Context
├── value = 21
└── access to outer lexical environment
4. The body executes
const result = value * multiplier;
The engine resolves:
value
inside the function.
It then resolves:
multiplier
from the outer lexical environment.
5. The function returns
return result;
produces:
42
The function execution context is no longer the running context.
6. Global execution continues
answer = 42
and then:
console.log(answer);
is executed.
This Also Explains Closures
Consider:
function createCounter() {
let count = 0;
return function increment() {
count++;
return count;
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
createCounter() has already returned.
Yet the inner function can still access:
count
Why?
Because JavaScript's lexical environment model allows the returned function to retain access to the environment it was created within.
That relationship forms the basis of a closure.
Conceptually:
increment()
↓
its lexical environment
↓
createCounter environment
↓
count
This is not merely a clever language feature.
It falls naturally out of how JavaScript resolves lexical bindings.
Where Does the Event Loop Fit?
This is another point where explanations often mix separate systems together.
The JavaScript engine executes JavaScript.
The surrounding runtime coordinates asynchronous capabilities.
Consider:
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
console.log("C");
The result is:
A
C
B
Why doesn't the engine simply pause at setTimeout?
Because the timer mechanism belongs to the host environment.
The runtime can arrange for the callback to become eligible for future execution while JavaScript continues running.
A simplified picture is:
JavaScript Engine
↕
Host Runtime
├── Timers
├── Network
├── Events
└── Task Scheduling
The ECMAScript language itself is not a complete I/O environment; host environments supply those external capabilities.
This distinction becomes especially important when comparing JavaScript running in:
Chrome
Node.js
Deno
Bun
embedded systems
other hosts
The language is JavaScript.
The host APIs and scheduling infrastructure can differ.
We'll examine the event loop, tasks, microtasks, promises, and async/await much more deeply in the next article.
What About Promises?
Consider:
console.log("Start");
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Understanding this requires two different layers:
Language-level concepts
JavaScript defines Promise objects and the semantics associated with promise reactions.
Runtime scheduling
The surrounding execution environment integrates these operations into its scheduling model.
That is why trying to explain asynchronous JavaScript using only the call stack is insufficient.
The stack tells us:
what is executing now
but asynchronous scheduling also needs to explain:
what may execute later
That deserves its own deep dive.
And What About Memory?
Executing JavaScript also requires memory.
Consider:
const user = {
name: "Amina",
articles: ["JavaScript", "Databases", "Networks"],
};
The engine needs memory for values and internal runtime structures.
As programs run, they continuously create data:
const result = {
id: crypto.randomUUID(),
createdAt: new Date(),
};
Some objects remain reachable.
Others eventually become unreachable.
JavaScript engines therefore contain garbage collectors that identify memory that can safely be reclaimed.
This lets us write:
function work() {
const temporary = {
data: new Array(1000),
};
}
work();
without manually freeing temporary.
Garbage collection is itself a large subject involving concepts such as:
reachability
generations
marking
sweeping
compaction
write barriers
incremental collection
concurrent work
It deserves an article of its own.
For now, the important idea is:
JavaScript execution is not only about CPU instructions. The engine must continuously manage memory as well.
So Is JavaScript Interpreted or Compiled?
The most accurate practical answer is:
Modern JavaScript engines use a combination of interpretation and compilation.
Calling JavaScript simply:
interpreted
is outdated.
Calling it simply:
compiled
also hides important details.
For V8, source code can be parsed and transformed into Ignition bytecode, initially interpreted, while additional compilation tiers can produce machine code based on execution behavior.
The useful model is:
Parse
↓
Start executing relatively quickly
↓
Observe runtime behavior
↓
Compile useful code
↓
Optimize hot code
↓
Deoptimize when assumptions fail
This is called a tiered execution architecture.
Why Doesn't the ECMAScript Specification Explain Ignition or TurboFan?
Because those are implementation details.
The specification tells engines what observable behavior must occur.
For example, it defines how:
let x = 5;
x++;
must behave.
It does not say:
You must compile this using Ignition.
That would prevent engines from experimenting with better implementations.
Different engines can therefore use different strategies while implementing the same JavaScript semantics.
For example, browser vendors can change:
parser architecture
bytecode representation
optimization tiers
intermediate representations
garbage collectors
machine-code generation
without changing your JavaScript source.
That separation is one of the reasons language implementations can improve dramatically over time.
Specification vs Engine vs Runtime
At this point, we can build a much better model.
ECMAScript Specification
Defines:
what JavaScript means
Examples:
syntax
variables
functions
objects
lexical environments
execution contexts
promises
language semantics
JavaScript Engine
Implements those semantics.
Examples of responsibilities include:
parsing
bytecode
compilation
optimization
garbage collection
machine execution
Host Runtime
Provides the world around JavaScript.
Examples can include:
timers
networking
DOM
filesystem
events
process APIs
Together:
┌──────────────────────────────────────────┐
│ Host Runtime │
│ │
│ APIs, timers, network, DOM/filesystem │
│ │
│ ┌──────────────────────────────────┐ │
│ │ JavaScript Engine │ │
│ │ │ │
│ │ Parser │ │
│ │ Execution engine │ │
│ │ Compiler │ │
│ │ Optimizer │ │
│ │ Garbage collector │ │
│ │ │ │
│ │ Implements ECMAScript semantics │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────────┘
This separation is one of the most useful mental models a JavaScript developer can learn.
The Complete Journey
Let's return to:
const price = 100;
const taxRate = 0.15;
function calculateTotal(price) {
return price + price * taxRate;
}
console.log(calculateTotal(price));
A deliberately simplified journey looks like this.
1. Source arrives
Characters
2. Scanner identifies lexical elements
const
price
=
100
...
3. Parser analyzes grammar
Program
├── declarations
├── function
└── call expression
4. Internal syntax representation is produced
For engines such as V8, parsing can produce an AST used by later compilation stages.
5. Execution environments are established
Bindings and execution contexts follow ECMAScript semantics.
6. Executable representation is generated
In V8:
AST
↓
Ignition bytecode
7. Execution begins
The interpreter can begin executing bytecode.
8. Functions are called
Execution contexts become active and return as calls enter and exit.
9. Runtime feedback is collected
V8 can observe runtime characteristics relevant to optimization.
10. Important code may receive faster compiled forms
Different V8 tiers balance compilation cost against execution speed.
11. Optimized code executes
The CPU ultimately executes machine instructions produced by the engine.
12. Assumptions may fail
If speculative optimization becomes invalid:
deoptimization
can occur.
13. Memory continues to be managed
Unreachable runtime data can eventually be reclaimed.
And all of that happened because we asked JavaScript for:
115
A Better Mental Model for JavaScript
When learning JavaScript, this model is enough:
Read code
↓
Execute code
As you become a stronger engineer, replace it with:
Source
↓
Parse
↓
Represent
↓
Prepare execution state
↓
Execute
↓
Observe
↓
Compile
↓
Optimize
↓
Possibly deoptimize
And when asynchronous operations enter the picture, expand it again:
JavaScript Engine
↕
Host Runtime
↕
Task Scheduling
↕
External Systems
Each deeper model explains behavior that the simpler model cannot.
That is a recurring pattern throughout software engineering.
Abstractions are useful.
But eventually, difficult bugs live underneath them.
Common Misconceptions
"JavaScript executes line by line."
Only as a rough source-level mental model.
The engine first performs substantial processing and environment setup, and modern engines can compile and optimize execution dynamically.
"JavaScript is an interpreted language."
Incomplete.
Modern engines combine interpretation and several forms of compilation. V8 currently uses a tiered architecture involving Ignition and multiple compilation tiers.
"Hoisting means JavaScript moves declarations to the top."
No.
That describes the observable effect poorly.
Bindings are established according to declaration-instantiation and lexical-environment semantics.
"The event loop is part of the JavaScript engine."
Not quite.
The language engine and host runtime are distinct concepts. ECMAScript itself does not define a complete external I/O environment.
"Optimized JavaScript stays optimized forever."
Not necessarily.
Speculative assumptions can become invalid and trigger deoptimization.
"All JavaScript engines work exactly like V8."
No.
V8 is one implementation.
Its concepts are useful for understanding a real modern engine, but implementation details should never be confused with requirements imposed by ECMAScript.
Why Should a Full-Stack Developer Care?
You rarely need to think about compiler tiers while building a CRUD form.
But understanding the execution model changes how you reason about JavaScript.
It helps explain:
scope
closures
call stacks
recursion
hoisting
temporal dead zones
asynchronous execution
runtime performance
memory behavior
optimization
deoptimization
browser and Node.js differences
Most importantly, it gives you a framework for learning the next layer.
Instead of memorizing:
letbehaves differently fromvar.
you can ask:
How are their bindings created and initialized?
Instead of memorizing:
JavaScript is single-threaded.
you can ask:
Which part is single-threaded, what does the host runtime do, and how are asynchronous operations scheduled?
Instead of memorizing:
V8 makes JavaScript fast.
you can ask:
What execution tiers exist, what information do they collect, and what tradeoffs are they making?
Those questions lead from framework knowledge toward engineering knowledge.
Final Mental Model
If you remember only one diagram from this article, remember this:
JavaScript Source
│
▼
Scan and Parse
│
▼
Internal Representation
│
▼
Prepare Execution Contexts
│
▼
Executable Bytecode
│
▼
Execute
│
Runtime Feedback
│
┌────────────┴────────────┐
▼ ▼
Keep Executing Optimize
│
▼
Machine Instructions
│
Assumption changes?
│
Yes ▼
Deoptimize
The exact implementation depends on the engine.
The core lesson does not:
Your JavaScript source is only the beginning. Between the code you write and the instructions executed by the processor sits an entire language implementation continuously parsing, managing execution state, observing behavior, compiling, optimizing, and managing memory.
Once you understand that, JavaScript stops looking like magic.
It starts looking like a system.
What's Next?
We've explained how JavaScript reaches execution.
But we deliberately left one major question unanswered:
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
Promise.resolve().then(() => {
console.log("C");
});
console.log("D");
Why does this not execute simply from top to bottom?
That takes us beyond the engine itself and into:
the call stack
tasks
microtasks
promises
host runtimes
asynchronous scheduling
That is the subject of the next article:
The JavaScript Event Loop: Call Stack, Microtasks, Macrotasks, and Everything Between
References
The technical model in this article is grounded primarily in the current ECMAScript specification and official V8 engineering documentation. The ECMAScript specification defines execution contexts and language semantics, while V8's documentation describes its implementation-specific parsing, Ignition bytecode, Sparkplug, Maglev, TurboFan, and adaptive optimization architecture.
Discussion