How JavaScript Is Executed: From Parsing to Event Loop
A practical, in-depth guide explaining how JavaScript gets parsed, compiled, and executed, how the call stack and event loop manage flow, and how async work is scheduled and observed in modern engines.
JavaScript execution unfolds in distinct phases: parsing, compilation, and runtime. A modern engine tokenizes and parses source into an AST, then compiles hot paths with a JIT, and finally executes on a single-threaded call stack. The event loop coordinates synchronous work with asynchronous tasks via microtasks and macrotasks, ensuring responsive applications.
How JavaScript Execution Starts
When a script loads, the engine begins by tokenizing and parsing the source, building an Abstract Syntax Tree (AST). The AST guides the interpreter or compiler as it translates code into executable instructions. In many engines, a preliminary parse runs quickly to establish scope, hoisting, and syntactic structure before any actual values are computed. In this section, we walk through a simple example to illustrate the flow from source text to the first console output.
console.log('start');
function sum(a,b){ return a+b; }
const result = sum(3,4);
console.log('result:', result);A second, short example shows how top-level statements prepare the global environment. Variables declared with let/const become part of the lexical scope, and function declarations are hoisted within their scope, affecting how the first lines execute as the script runs.
let x = 5;
x += 10;
console.log(x); // 15Steps
Estimated time: 20-40 minutes
- 1
Enable DevTools and load sample script
Open your browser's DevTools and load the sample script from the examples above to observe parsing and initial execution in the console. This establishes the global environment and baseline behavior.
Tip: Pin the Console or skim the Sources panel for quick context. - 2
Examine global vs local scope
Walk through the global context creation, then enter a function to see how lexical environments and hoisting affect variable visibility.
Tip: Use breakpoints to inspect variable bindings at different depths. - 3
Trigger async tasks and observe queues
Run code that uses Promises and setTimeout to witness microtask vs macrotask scheduling and the order of outputs.
Tip: Remember microtasks run before macrotasks after the current stack clears. - 4
Use debugger statements to inspect stacks
Insert debugger; to pause execution at a critical point and view the call stack and local scope.
Tip: Combine with console.table or console.dir for richer views. - 5
Profile hot paths and optimize
Switch to Performance profiling to identify long-running functions and optimize hot paths or reduce allocations.
Tip: Aim to minimize allocations inside tight loops and hot functions.
Prerequisites
Required
- Required
- Required
- Required
- Basic knowledge of JavaScript (variables, functions, scope)Required
Optional
- Command line basicsOptional
Keyboard Shortcuts
| Action | Shortcut |
|---|---|
| Open DevToolsIn Chrome/Edge/Firefox | Ctrl+⇧+I |
| Open ConsoleDevTools Console | Ctrl+⇧+J |
| Inspect ElementsElement Picker | Ctrl+⇧+C |
| Pause ScriptPause the debugger for the current script | F8 |
| Step OverMove to next line in current function | F10 |
| Step IntoDrill into a function | F11 |
| Step OutExit current function | ⇧+F11 |
| Refresh without CacheReload page bypassing cache | Ctrl+⇧+R |
Questions & Answers
What is the event loop and why does it matter in execution?
The event loop coordinates synchronous JavaScript work with asynchronous callbacks. It ensures the single thread remains responsive by queuing tasks and processing microtasks before macrotasks. Understanding it helps predict code timing and avoid race conditions.
The event loop is what coordinates all the work JavaScript does behind the scenes, including when functions run and when asynchronous tasks complete.
Is JavaScript multi-threaded?
JavaScript itself runs on a single thread, but browsers provide workers for background tasks. Outside of workers, all code executes in one thread, which is why async APIs and event-based design are important for responsiveness.
JavaScript runs on one main thread, but you can use workers for parallel tasks.
Hoisting and its effect on execution
Hoisting moves declarations to the top of their scope, but not initializations. This affects how code executes, especially with var versus let/const. It’s important to understand to avoid surprises in function scope and loops.
Hoisting means declarations are processed before code runs, which changes when variables appear in scope.
What’s the difference between var, let, and const in scope?
Var is function-scoped and can be redeclared; let and const are block-scoped, with let allowing reassignment and const preventing reassignment. Understanding scope helps prevent leaks and bugs in loops and closures.
Var is function-scoped; let and const are block-scoped, with const being immutable after assignment.
How do Promises and microtasks relate to the event loop?
Promises schedule microtasks, which run after the current call stack clears but before the next macrotask. This sequencing defines when thenCallbacks execute relative to setTimeout and other timers.
Promises run as microtasks after the current code finishes, before waiting tasks run.
Why would a modern engine optimize code with JIT?
JIT compilers identify hot paths and replace them with optimized machine code, dramatically speeding repeated operations. Deoptimization can occur if assumptions about types or shapes change at runtime.
JIT makes hot code run faster by compiling it on the fly, but may adjust if assumptions change.
What to Remember
- Master the execution flow: parsing, compilation, running
- Event loop governs async vs sync ordering
- DevTools is essential for tracing stack frames and scope
- Avoid patterns that hinder optimization (eval, dynamic code)
- Use proper iteration variables to prevent closure issues
