JavaScript is Single-Threaded

JavaScript has a single call stack and executes one operation at a time. There is no parallel execution within JS (Web Workers are a separate exception). This means:

  • If you run a heavy computation, the browser UI freezes until it finishes
  • No two pieces of JS code run simultaneously in the same thread
  • The event loop is how JS handles async operations without blocking

The Components of the Event Loop

JavaScript runtime components ┌─────────────────────────────────────────────────────┐ │ Browser / Node.js │ │ ┌──────────────┐ ┌──────────────────────────┐ │ │ │ Call Stack │ │ Web APIs │ │ │ │ (JS) │ │ setTimeout, fetch, │ │ │ │ │ │ DOM events, etc. │ │ │ └──────┬───────┘ └────────────┬─────────────┘ │ │ │ │ (callbacks) │ │ ┌──────▼───────────────────────▼──────────────┐ │ │ │ Event Loop │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │ │ Microtask Queue (Promises, queueMicrotask)│ │ │ │ ├─────────────────────────────────────┤ │ │ │ │ │ Task Queue (setTimeout, DOM events) │ │ │ │ │ └─────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────┘

The Call Stack

The call stack is a LIFO (Last In, First Out) data structure. When a function is called, a stack frame is pushed. When it returns, the frame is popped. JavaScript executes the function on top of the stack.

Call stack visualised function multiply(a, b) { return a * b; } function square(n) { return multiply(n, n); } function printSquare(n) { const result = square(n); console.log(result); } printSquare(5); // Call stack evolution: // 1. [printSquare(5)] // 2. [printSquare(5), square(5)] // 3. [printSquare(5), square(5), multiply(5,5)] // 4. [printSquare(5), square(5)] ← multiply returns 25 // 5. [printSquare(5)] ← square returns 25 // 6. [] ← printSquare logs 25, returns

Never Block the Call Stack

If synchronous code runs for more than ~16ms (one frame at 60fps), the browser cannot repaint — the UI appears frozen. Never run heavy loops, large JSON.parse(), or synchronous file I/O on the main thread. Use Web Workers for CPU-heavy tasks, and async APIs for I/O.

Web APIs — Where Async Work Happens

Web APIs are provided by the browser (or Node.js libuv runtime) and run outside of the JavaScript call stack. When you call setTimeout(fn, 1000), you hand the timer off to the browser. JavaScript moves on immediately. After 1000ms, the browser places fn in the task queue.

This is the key insight: JavaScript never waits. It delegates async work to the runtime environment, registers a callback, and continues executing. When the async work completes, the callback is queued.

Task Queue vs Microtask Queue

PropertyTask Queue (Macrotask)Microtask Queue
SourcessetTimeout, setInterval, DOM events, I/O callbacksPromise .then/.catch/.finally, queueMicrotask, MutationObserver
PriorityLowerHigher — runs FIRST
Drain behaviourOne task per event loop tickALL microtasks drain before next task
Can starve tasks?No — always gets its turnYes — infinite microtask loop blocks tasks
Execution order — microtasks before tasks console.log('1 - synchronous'); setTimeout(() => console.log('2 - setTimeout (task queue)'), 0); Promise.resolve().then(() => console.log('3 - Promise (microtask)')); Promise.resolve().then(() => console.log('4 - Promise (microtask)')); console.log('5 - synchronous'); // Output order: // 1 - synchronous (call stack) // 5 - synchronous (call stack) // 3 - Promise (microtask — runs BEFORE setTimeout) // 4 - Promise (microtask — ALL microtasks drain first) // 2 - setTimeout (task queue — runs last)

How the Event Loop Works — Step by Step

  1. Execute all synchronous code in the call stack until it is empty
  2. Check the microtask queue — execute ALL microtasks (drain completely)
  3. If any new microtasks were added during step 2, drain those too
  4. Pick ONE task from the task queue and execute it (push onto call stack)
  5. Go back to step 2 — drain microtasks again
  6. Repeat

async/await and the Event Loop

async/await is syntactic sugar over Promises. Understanding how it maps to the event loop demystifies its behaviour:

async/await execution flow async function fetchData() { console.log('A - before await'); // synchronous const data = await fetch('/api/data'); // suspends here console.log('B - after await'); // runs as microtask when fetch resolves return data; } console.log('1 - before call'); fetchData(); console.log('2 - after call'); // Output: // 1 - before call // A - before await (fetchData runs synchronously up to await) // 2 - after call (fetchData is suspended; main thread continues) // B - after await (when fetch resolves, continuation is queued as microtask)

Node.js Event Loop Differences

Node.js uses the same event loop concept but with more phases (powered by libuv):

PhaseWhat Runs
timerssetTimeout and setInterval callbacks (when delay expires)
I/O callbacksCallbacks for I/O operations (file, network) from previous iteration
idle, prepareInternal Node.js use
pollRetrieves new I/O events; executes I/O-related callbacks
checksetImmediate() callbacks — runs after poll phase
close callbacksClose event callbacks (e.g. socket.on('close', ...))

Node.js also has process.nextTick() — which runs BEFORE the microtask queue (even before Promises). This makes it the highest-priority async mechanism in Node.

Debugging Async Order Issues

When async code runs in an unexpected order, map it to queues: synchronous first, then microtasks (Promises), then tasks (setTimeout). If you need something to run "as soon as current sync code finishes but before I/O", use queueMicrotask() or Promise.resolve().then(). If you need it "after rendering/I/O", use setTimeout(fn, 0).

How We Research and Update This Guide

We test the underlying formula or workflow, compare outputs with reliable references, and revise examples whenever the page content changes.

  • The workflow or formula is tested directly in the tool and compared against independent reference examples.
  • Examples are kept practical so readers can verify the result without hidden assumptions.
  • Pages are revised whenever the interface, calculation flow, or surrounding guidance materially changes.

Frequently Asked Questions — JavaScript Event Loop