Q15 of 40 · JavaScript

How does JavaScript's event loop enable asynchronous programming on a single thread?

JavaScriptMidjavascriptevent-loopasyncsingle-threadmicrotasksmacrotasks

Short answer

Short answer: JavaScript has one call stack. Async operations are handed off to the runtime. When they complete, callbacks are queued. The event loop picks tasks from the queue only when the stack is empty, allowing long I/O to run without blocking UI or other work.

Detail

JavaScript is single-threaded — there is only one call stack. Only one piece of code runs at a time. But network requests and timers must not block that thread.

How it works:

  1. Synchronous code runs directly on the call stack.
  2. Async APIs (setTimeout, fetch, fs.readFile) are handed off to the runtime's C++ thread pool or OS async I/O.
  3. When the operation completes, a callback (or Promise resolution) is placed on a queue.
  4. The event loop monitors the call stack. When it is empty, it picks the next item from the queue.

Queue priority:

  • Microtask queue (Promise callbacks, queueMicrotask) — drained completely before the next macro task
  • Macro-task queue (setTimeout, setInterval, I/O callbacks) — one task per loop iteration

Implication for tests: A test that does not await an async operation may pass before the operation completes. Playwright's auto-awaiting and Cypress's command queue exist to manage this problem.

// EXAMPLE

console.log("1 - start");

setTimeout(() => console.log("4 - setTimeout"), 0);

Promise.resolve().then(() => console.log("3 - microtask"));

console.log("2 - end of sync");

// Output order:
// 1 - start
// 2 - end of sync
// 3 - microtask   ← microtask queue drained before setTimeout
// 4 - setTimeout

// WHAT INTERVIEWERS LOOK FOR

The call stack + event loop + queue model. Distinction between microtask queue (Promises) and macro-task queue (setTimeout). Connecting to test reliability — why awaiting is required — shows applied understanding.

// COMMON PITFALL

Saying JavaScript is 'multi-threaded' because it feels concurrent. The concurrency is in the I/O runtime (C++ threads), not the JS engine — the script thread is always single-threaded.