How does JavaScript handle asynchronous operations? What mechanisms does it use
JS is single-threaded; async work uses the event loop. Mechanisms: Web APIs / Node libuv (timers, I/O, network) → task queues (macrotasks for setTimeout, I/O; microtasks for Promises, queueMicrotask). Event loop picks one task → drains microtasks → renders → repeat. Workers give true parallelism. AbortController for cancellation.
Single-threaded language, async runtime
JS itself runs on one thread. Async happens because the host (browser, Node) offloads work to other threads/processes (network stack, timer queue, file system) and notifies JS when results are ready via the event loop.
The pieces
[ Call stack ] ← synchronous code
↑
[ Event loop ] ← single coordinator
↑
[ Microtask Q ] ← Promise callbacks, queueMicrotask, MutationObserver
[ Macrotask Q ] ← setTimeout, setInterval, I/O, postMessage
[ Render queue ] ← rAF, paint, style/layout (browser only)
↑
[ Web APIs / libuv ] ← timers, fetch, fs, networkHow a tick proceeds
- Pop one macrotask, run to completion.
- Drain the microtask queue (run all microtasks queued during step 1, plus any they enqueue).
- (Browser) Style/layout/paint if needed.
- Repeat.
Implication: a chain of .thens runs entirely between two setTimeouts.
Mechanisms
Promises
The standardized async primitive. Resolved/rejected state, .then queues a microtask.
async/await
Sugar over Promises. Each await schedules continuation on the microtask queue.
setTimeout / setInterval
Macrotask after a minimum delay. Clamped to ~4ms minimum for nested timeouts in browsers.
I/O callbacks (Node) / fetch resolution
Network completes off-thread; callback queued as a (mostly) macrotask in Node libuv phases (timers, pending, poll, check, close).
queueMicrotask
Manually enqueue a microtask — useful for "run after current sync code but before next macrotask."
requestAnimationFrame
Browser-only. Callback runs before paint, ~60Hz on standard displays. Best for visual updates.
requestIdleCallback
Callback runs when the browser is idle. For non-urgent background work.
MessageChannel / postMessage
Cross-thread messaging (Workers, iframes). Also useful for fast macrotask scheduling (MessageChannel is the canonical "schedule task ASAP" hack).
Web Workers / Worker Threads
True parallelism. Separate heap, communicate via postMessage.
SharedArrayBuffer + Atomics
Shared memory between Worker threads with atomic operations.
AbortController
Cancel an in-flight async operation (fetch, etc.).
Microtask vs macrotask order
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// 1, 4, 3, 2 — microtask before next macrotaskLong-running work problems
JS is single-threaded, so a long sync loop blocks rendering AND further async resolution. Either:
- Yield (split with await + setTimeout).
- Web Worker (offload).
- Suspend non-urgent work (
startTransition,requestIdleCallback).
Node specifics
Node's event loop has phases (timers, pending callbacks, poll, check, close). process.nextTick is a Node-specific microtask that fires before Promise microtasks within the same phase. Avoid in new code; prefer queueMicrotask.
Interview framing
"JS is single-threaded; async comes from the host offloading to other threads and dispatching callbacks via the event loop. Tasks split into macrotasks (setTimeout, I/O) and microtasks (Promises, queueMicrotask). The loop runs one macrotask, drains all microtasks, optionally paints, repeats. Promises and async/await are sugar over the microtask queue. For true parallelism use Workers. For cancellation, AbortController. The classic ordering: sync code → microtasks → next macrotask, which is why a Promise.then prints before a setTimeout(0)."
Follow-up questions
- •Walk me through the event loop order with a tricky example.
- •What's the difference between microtask and macrotask?
- •How does Node's event loop differ?
Common mistakes
- •Treating setTimeout(0) as 'immediate' — microtasks run first.
- •Blocking the event loop with long sync work.
- •Mixing nextTick and microtasks.
Performance considerations
- •Anything > 50ms on the main thread blocks interaction. Use Workers for heavy compute, transitions for non-urgent renders.
Edge cases
- •Microtask infinite loop starves rendering.
- •Nested setTimeout clamps to 4ms.
- •process.nextTick ordering in Node.
Real-world examples
- •React 18 startTransition, web-vitals INP measurement, Node event loop debugging.