February 15, 2026 • 8 min read
JavaScript Event Loop Explained with Interactive Examples
Why this matters
JavaScript runs on a single call stack, but async APIs make it feel concurrent. The event loop is the mechanism that decides what runs next.
Rule to remember: when the call stack becomes empty, JavaScript drains the microtask queue first, then runs one task from the task queue, then repeats.
How JavaScript works (quick mental model)
JavaScript runs in a top-down, sequential way because it is single-threaded in the main runtime. That means statements are executed one by one on the call stack.
When async code is involved, the common execution pattern looks like:
- Synchronous code
- Promise callbacks (microtasks)
- Timeout and interval callbacks (tasks/macrotasks)
The three building blocks
- Call Stack: current execution context. Synchronous code runs here.
- Task Queue (Macrotask Queue): a FIFO queue for
setTimeout,setInterval, DOM events, and I/O callbacks. - Microtask Queue: a FIFO queue for
Promise.then/catch/finally,queueMicrotask, andMutationObserver.
The call stack itself behaves like LIFO (Last In, First Out), while both queues behave like FIFO (First In, First Out).
Example 1: Simple ordering
The code mixes sync logs, a Promise callback, and a setTimeout(0) callback.
Interactive Event Loop Demo
Simple Example
Synchronous logs vs Promise microtask vs setTimeout task
Code
console.log('A');
setTimeout(() => {
console.log('B - timeout');
}, 0);
Promise.resolve().then(() => {
console.log('C - promise');
});
console.log('D');Output Challenge
Test Yourself
Write the console output in order, one line per entry.
Step 1 / 8: Script starts on call stack
Global script enters the call stack.
Call Stack (Top First)
Microtask Queue (Front First)
No microtasks waiting
Task Queue (Front First)
No tasks waiting
Console Output So Far
No console output yet.
Example 2: Slightly complex ordering
This version nests a microtask inside a timeout and a timeout inside a microtask.
Interactive Event Loop Demo
Complex Example
Nested microtasks and tasks with ordering across turns
Code
console.log('1');
setTimeout(() => {
console.log('2 - timeout 1');
Promise.resolve().then(() => {
console.log('3 - microtask inside timeout');
});
}, 0);
Promise.resolve().then(() => {
console.log('4 - microtask 1');
setTimeout(() => {
console.log('5 - timeout inside microtask');
}, 0);
});
console.log('6');Output Challenge
Test Yourself
Write the console output in order, one line per entry.
Step 1 / 10: Script enters stack
Global script begins.
Call Stack (Top First)
Microtask Queue (Front First)
No microtasks waiting
Task Queue (Front First)
No tasks waiting
Console Output So Far
No console output yet.
Example 3: Timeout is not zero
This shows that setTimeout(fn, 1000) means at least 1000ms, not exactly 1000ms.
Interactive Event Loop Demo
Timeout Not Zero Example
Why setTimeout with 1000ms is a minimum delay, not exact timing
Code
const start = Date.now();
setTimeout(() => {
console.log('T1 ~1000ms', Date.now() - start);
}, 1000);
setTimeout(() => {
console.log('T2 ~0ms', Date.now() - start);
}, 0);
Promise.resolve().then(() => {
console.log('Microtask', Date.now() - start);
});
console.log('Sync end', Date.now() - start);Output Challenge
Test Yourself
Write the console output in order, one line per entry.
Step 1 / 8: Script starts
Global script enters stack and sets reference start time.
Call Stack (Top First)
Microtask Queue (Front First)
No microtasks waiting
Task Queue (Front First)
No tasks waiting
Console Output So Far
No console output yet.
Final checklist
- Synchronous code runs first on the call stack.
- After stack is empty, all microtasks run before the next task.
setTimeout(..., 0)still waits for a future task turn.- Timeout delay values are minimum thresholds, not exact execution timestamps.