What is the event loop in JavaScript?

Intermediate15 min interview
Skills tested:
Call stack and execution context mechanicsMicrotask queue vs macrotask queue orderingWhy JavaScript is non-blocking despite being single-threadedPredicting async output order in mixed Promise and setTimeout codeHow the event loop interacts with Web APIs and Node.js APIs

Advertisement

🧩 Scenario

In a real codebase, understanding the event loop is essential for debugging async ordering bugs, understanding why UI freezes when synchronous code runs too long, and reasoning about the difference in timing between Promise callbacks and setTimeout callbacks. It is one of the most commonly asked JavaScript interview topics at mid and senior levels because it tests whether a candidate understands JavaScript concurrency at a runtime level rather than just knowing syntax.

Architecture Walkthrough

The Call Stack and Single-Threaded Execution

JavaScript is single-threaded: it can only execute one thing at a time. The call stack tracks the current execution context. When a function is called, a new frame is pushed onto the stack. When the function returns, that frame is popped off. All synchronous code runs in this stack, and no other code can execute while a frame is on the stack.

If a synchronous operation takes a long time (a large loop, a heavy computation), the call stack remains occupied for that entire duration. During this time, no event handlers, no timer callbacks, and no Promise continuations can run. This is what "blocking the main thread" means, and it is the root cause of UI freezes and unresponsive pages.

Web APIs, Task Queues, and the Event Loop

When JavaScript calls a Web API like setTimeout, fetch, or addEventListener, the work is handed off to the browser's (or Node.js's) runtime environment, which handles it outside the JavaScript thread. When the runtime work completes (timer fires, network responds, user clicks), the callback is placed in a queue.

The event loop is a continuous process that checks one condition: is the call stack empty? If yes, it takes the next task from a queue and pushes it onto the stack for execution. This is how JavaScript achieves non-blocking behavior without threads: by deferring completion callbacks to run between synchronous execution blocks.

Microtasks vs Macrotasks

There are two kinds of queues. The microtask queue holds Promise callbacks (.then, .catch, .finally) and queueMicrotask calls. The macrotask queue (also called the task queue) holds setTimeout, setInterval, setImmediate (Node.js), and I/O callbacks.

The critical ordering rule is: after each macrotask completes, the event loop drains the entire microtask queue before picking up the next macrotask. This means that no matter how many .then callbacks are chained, they all run to completion before the next setTimeout callback starts. This is why Promise.resolve().then(...) always logs before setTimeout(..., 0) even when both are scheduled at the same moment.


Key Code Explained

console.log('1: synchronous');

setTimeout(() => console.log('2: macrotask (setTimeout)'), 0);

Promise.resolve()
  .then(() => console.log('3: microtask (Promise)'))
  .then(() => console.log('4: chained microtask'));

queueMicrotask(() => console.log('5: microtask (queueMicrotask)'));

console.log('6: synchronous');

// Output order:
// 1: synchronous
// 6: synchronous
// 3: microtask (Promise)
// 5: microtask (queueMicrotask)
// 4: chained microtask
// 2: macrotask (setTimeout)

Walking through the execution: lines 1 and 6 run synchronously on the call stack. The setTimeout callback is registered with the browser runtime and placed in the macrotask queue. The Promise.resolve().then() callbacks and the queueMicrotask callback are placed in the microtask queue. After the call stack empties, the microtask queue drains completely (items 3, 5, and the chained item 4). Only then does the event loop pick up the macrotask at item 2.


Tradeoffs

Queue typeExamplesWhen it runs
Synchronous (call stack)Regular function calls, loopsImmediately, blocking all other execution
Microtask queuePromise.then, queueMicrotaskAfter current task, before next macrotask, full drain
Macrotask queuesetTimeout, setInterval, I/OOne per event loop tick, after microtask queue empties

What Interviewers Actually Check

  • Whether you can correctly predict the output order of mixed sync, Promise, and setTimeout code
  • Whether you know that microtasks drain completely before the next macrotask runs
  • Whether you understand why setTimeout(fn, 0) does not mean "run immediately"
  • Whether you can explain the consequence of blocking the call stack with synchronous code
  • Whether you can name real examples of microtasks and macrotasks

Follow-Up Questions

  1. What is queueMicrotask and how does it differ from wrapping code in Promise.resolve().then()?
  2. How does the Node.js event loop differ from the browser event loop? What is the nextTick queue?
  3. If you nest a setTimeout inside a Promise.then, what order does the callback run relative to other pending microtasks?
  4. What happens to a setInterval callback when the call stack is blocked for longer than the interval duration?
  5. A frontend engineer reports that their React UI feels janky when processing a large array. How would you explain the event loop's role in the problem and propose a fix?

Common Candidate Mistakes

  • Thinking setTimeout(fn, 0) executes the callback immediately when it always goes to the macrotask queue
  • Not knowing that all microtasks drain before the next macrotask, which causes incorrect output order predictions
  • Confusing the call stack (where code executes) with the task queue (where pending callbacks wait)
  • Assuming Node.js and browser event loops are identical when Node.js has additional phases like process.nextTick
  • Saying JavaScript is "asynchronous" without clarifying that it is single-threaded and only appears concurrent through event loop scheduling

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you describe the call stack, microtask queue, and macrotask queue and how each feeds into the event loop?
  • Can you predict the output order of code that mixes synchronous statements, setTimeout, and Promise.resolve()?
  • Can you explain why Promise callbacks run before setTimeout callbacks even when setTimeout has zero delay?
  • Can you describe what happens when the call stack is blocked with a long synchronous operation?
  • Can you name at least two examples of microtasks and two examples of macrotasks?

Summary

The JavaScript event loop is the mechanism that enables non-blocking asynchronous behavior in a single-threaded runtime. Synchronous code runs on the call stack. Async work is offloaded to the runtime environment (browser APIs or Node.js), and completion callbacks are placed in queues: the microtask queue for Promise callbacks and the macrotask queue for timers and I/O.

The event loop's rule is simple: when the call stack is empty, drain all microtasks, then pick one macrotask. This ordering means that Promise continuations always run before the next timer callback, regardless of when each was scheduled.

Understanding this model is necessary for debugging async ordering bugs, avoiding UI jank caused by long synchronous operations, and reasoning about why setTimeout(fn, 0) does not mean "run now." It is one of the most reliable signals of deep JavaScript knowledge in an interview.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

Does JavaScript run multiple threads?

No. JavaScript is single-threaded but uses asynchronous callbacks managed by the event loop to avoid blocking.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement