Introduction: The Paradox
Here's a question that confuses many developers: If JavaScript is single-threaded, how can hoisting work? Shouldn't statements execute sequentially from top to bottom?
// This actually works!
console.log(message); // Output: undefined (NOT an error)
var message = "Hello!";This article explains the paradox: How JavaScript can be single-threaded yet not strictly sequential.
What Does "Single-Threaded" Actually Mean?
Single-threaded means JavaScript can only execute one piece of code at a time. It cannot run multiple tasks in parallel.
Single-Threaded Languages
- JavaScript (in browsers and Node.js)
- Python (with the GIL - Global Interpreter Lock)
- Ruby
Multi-Threaded Languages
- Java
- C++
- Go
- Rust
Being single-threaded doesn't mean sequential! This is the key insight.
Sequential vs Single-Threaded: What's the Difference?
| Characteristic | Sequential Execution | Single-Threaded (JavaScript) |
|---|---|---|
| Execution Order | Top-to-bottom, line by line | Determined by execution phases + event loop |
| Pre-processing Phase | None | Hoisting (creation phase before execution) |
| Concurrent Tasks | Strictly one at a time | One at a time (but with async patterns) |
| Code Example | Always executes in source code order | Declarations executed in creation phase |
The Two Phases of JavaScript Execution: The Secret
JavaScript execution happens in two distinct phases:
Phase 1: Creation (Compilation) Phase
Before any code executes, JavaScript's engine:
- Scans the entire code
- Creates variables and function declarations in memory
- Sets up the execution context
- Does NOT execute any code yet
Phase 2: Execution Phase
After creation, the engine:
- Executes statements line by line
- Assigns values to variables
- Calls functions
Visual Representation of the Two Phases
Let's Walk Through Examples
Example 1: Function Declaration Hoisting
// What you write:
console.log(greet()); // "Hello!"
function greet() {
return "Hello!";
}
// PHASE 1 (Creation):
// function greet() { return "Hello!"; } → Hoisted completely
// PHASE 2 (Execution):
// console.log(greet()); → Calls function → "Hello!"Example 2: var Hoisting (The Confusing Part)
// What you write:
console.log(message); // undefined (not an error!)
var message = "Hello!";
console.log(message); // "Hello!"
// PHASE 1 (Creation):
// var message = undefined; → Hoisted and initialized as undefined
// PHASE 2 (Execution):
// console.log(message); → undefined
// message = "Hello!"; → Assignment
// console.log(message); → "Hello!"Example 3: const/let Hoisting (Temporal Dead Zone)
// What you write:
console.log(name); // ❌ ReferenceError
const name = "Alice";
// PHASE 1 (Creation):
// const name = <uninitialized>; → Hoisted but NOT initialized (TDZ)
// PHASE 2 (Execution):
// console.log(name); → ❌ Error: Still in TDZ!
// const name = "Alice"; → Now initialized, TDZ endsThe Event Loop: How Single-Threaded Execution Feels Asynchronous
The Three Components
1. Call Stack - Where function calls are executed. Only one function at a time.
2. Web APIs / Task Queue - Browser APIs (setTimeout, fetch) that run outside JavaScript. Callbacks go to Task Queue.
3. Event Loop - Checks if call stack is empty, then moves tasks from queue to stack.
Event Loop Example
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
console.log("End");
// Output:
// Start
// End
// Timeout
// Why? Even though setTimeout has 0ms delay:
// 1. "Start" - executed immediately
// 2. setTimeout pushed to Web API (not on call stack)
// 3. "End" - executed immediately
// 4. Call stack empty → event loop moves setTimeout callback
// 5. "Timeout" - executedMicrotask vs Macrotask Queue
console.log("Start");
setTimeout(() => console.log("Timeout"), 0); // Macrotask
Promise.resolve()
.then(() => console.log("Promise")); // Microtask
console.log("End");
// Output:
// Start
// End
// Promise ← Microtasks run first!
// Timeout ← Macrotasks run after microtasks emptyKey Interview Question: Explain the Contradiction
📝 Model Answer
The Question: "If JavaScript is single-threaded, why doesn't it execute statements sequentially?"
The Answer: "JavaScript is single-threaded, meaning only one operation executes at a time. However, 'single-threaded' doesn't mean 'strictly sequential.'
JavaScript uses a two-phase execution model:
- Creation Phase: The engine scans code and hoists declarations before any execution
- Execution Phase: Code runs line by line
Additionally, JavaScript's Event Loop allows asynchronous operations, and it prioritizes microtasks (Promises) over macrotasks (setTimeout)."
Summary: The Two-Phase Model
| Phase | What Happens | Execution Order |
|---|---|---|
| Creation | Engine scans code, hoists declarations, creates execution context | NOT sequential - all declarations processed first |
| Execution | Code runs line by line, values assigned, functions called | Sequential within this phase |
| Event Loop | Manages async callbacks, microtasks, macrotasks | Priority-based (microtasks before macrotasks) |
Key Takeaways
🎯 Remember This
- Single-threaded ≠ Sequential - JavaScript has a sophisticated execution model
- Two-Phase Execution: Creation phase (hoisting) happens before execution phase
- Function declarations are fully hoisted - available before their definition
- var is hoisted and initialized as undefined - so no error, just undefined
- const/let exist in the Temporal Dead Zone - hoisted but not initialized
- Event Loop manages asynchronous execution - non-blocking but still single-threaded
- Microtasks (Promises) run before Macrotasks (setTimeout)
Conclusion
JavaScript is single-threaded, but it's not strictly sequential. The key to understanding this apparent paradox is recognizing that JavaScript execution happens in two phases: the Creation Phase (where hoisting occurs) and the Execution Phase (where statements run sequentially). The Event Loop adds another layer of complexity to execution order.
You Now Understand JavaScript's Execution Model 👆
From hoisting paradoxes to the event loop—you've unlocked knowledge that separates junior from senior developers.
Join Cohort 3 Waitlist →