JavaScript runs on a single thread — it can only do one thing at a time. Yet a page can wait on a network request, a timer, and a click handler all at once, without freezing. The event loop is the mechanism that makes this possible.
Every function call gets pushed onto the call stack, and popped off when it returns. JavaScript only ever runs what's on top of the stack.
function greet() {
console.log('Hello')
}
greet()
// greet() is pushed, runs, then poppedA function like setTimeout hands its callback off to the browser, not the call stack — the call stack stays free to keep running the rest of the script immediately.
console.log('1')
setTimeout(() => console.log('2'), 0)
console.log('3')
// Output: 1, 3, 2 — even with a 0ms delay,
// the callback waits until the call stack is emptyOnce a timer, network request, or event fires, its callback doesn't interrupt the call stack directly — it waits in a queue. The event loop's job is simple: check if the call stack is empty, and if so, move the next callback in queue onto it.
Not all queued callbacks are treated equally. Promise callbacks go into a microtask queue, which the event loop always fully empties before touching the macrotask queue (setTimeout, setInterval, DOM events).
console.log('1')
setTimeout(() => console.log('2 — macrotask'), 0)
Promise.resolve().then(() => console.log('3 — microtask'))
console.log('4')
// Output: 1, 4, 3, 2 — microtasks always run before the next macrotask| Queue | Examples | Priority |
|---|---|---|
| Microtask | Promise .then()/.catch(), async/await continuation | Fully drained before the next macrotask |
| Macrotask | setTimeout, setInterval, DOM events, I/O | One runs per event loop cycle |
Practice it interactively
This site's Event Loop tool (under Tools) animates the call stack, task queue, and microtask queue step by step for a piece of code — a much faster way to build intuition than reading the ordering rules alone.