DEV Community

Cover image for Stop Guessing the Event Loop: Build a JavaScript Timeline Test for Interviews
Karuha
Karuha

Posted on Originally published at aceround.app

Stop Guessing the Event Loop: Build a JavaScript Timeline Test for Interviews

If you can predict one short program's output and explain why, you can answer most JavaScript event-loop interview follow-ups without memorizing a diagram. Start with synchronous code, drain microtasks, then let the runtime take the next task. The useful preparation step is to make that order executable.

JavaScript event-loop flow: synchronous code, microtasks, then timers

Most event-loop answers fail because they begin with vocabulary: "macrotasks, microtasks, call stack..." The interviewer then adds one Promise.then() inside a timer and the explanation turns into guesswork.

A better answer has two parts:

  1. State the ordering rule.
  2. Prove it with the smallest program that exercises it.

This is a 15-minute drill for exactly that.

What should you say before writing code?

Use a precise, short opening:

JavaScript runs the current synchronous work to completion. Before the runtime takes another task such as a timer callback, it drains the microtask queue. Promise reactions and queueMicrotask use that queue; setTimeout schedules a later task.

That is a strong starting point, not a universal promise about every host API. Browsers and Node.js provide different APIs and have runtime-specific phases. In an interview, say which environment you are discussing when the question gets specific.

The practical consequence is simple: a resolved promise does not interrupt currently running synchronous code, but it usually runs before a zero-delay timer scheduled in that same turn.

Can you prove the timeline instead of narrating it?

Save this as event-loop-drill.mjs and run node event-loop-drill.mjs. It uses only Node's built-in assertion module.

import assert from "node:assert/strict";

const events = [];
const mark = (label) => events.push(label);

mark("sync: start");

setTimeout(() => {
  mark("timer: initial");
}, 0);

queueMicrotask(() => {
  mark("microtask: queueMicrotask");
});

Promise.resolve().then(() => {
  mark("microtask: promise.then");
  setTimeout(() => {
    mark("timer: scheduled by microtask");
  }, 0);
});

mark("sync: end");

await new Promise((resolve) => setTimeout(resolve, 25));

assert.deepEqual(events, [
  "sync: start",
  "sync: end",
  "microtask: queueMicrotask",
  "microtask: promise.then",
  "timer: initial",
  "timer: scheduled by microtask",
]);

console.log("event-loop timeline assertions passed");
Enter fullscreen mode Exit fullscreen mode

The result has five useful facts:

Observation What it demonstrates
Both sync labels appear first The current stack finishes before callbacks run.
queueMicrotask precedes Promise.then They were inserted into one FIFO microtask queue in that order.
Both microtasks precede the first timer The microtask queue drains before the next timer task.
The initial timer precedes the nested timer The first timer was scheduled earlier.
A 25 ms wait appears only in the test harness It gives both timer callbacks time to run; it is not part of the ordering rule.

The assertion is the important bit. A console log is easy to glance at and misread. A failing assertion gives you a concrete question: did the code, the environment, or my assumption differ?

Which follow-up questions expose shallow understanding?

After you can predict the first script, change one thing at a time.

What if a timer creates a promise?

Move the promise into the first timeout:

setTimeout(() => {
  mark("timer: first");
  Promise.resolve().then(() => mark("microtask: after first timer"));
}, 0);

setTimeout(() => {
  mark("timer: second");
}, 0);
Enter fullscreen mode Exit fullscreen mode

The useful explanation is not merely "promises are faster." It is: after the first timer callback returns, the runtime drains the microtasks it created before continuing to the next available task. That is why the promise reaction belongs between the two timer callbacks.

What if a microtask keeps scheduling microtasks?

This is the operational edge case:

let remaining = 3;

queueMicrotask(function repeat() {
  mark(`microtask: ${remaining}`);
  remaining -= 1;
  if (remaining > 0) queueMicrotask(repeat);
});
Enter fullscreen mode Exit fullscreen mode

With a bounded counter, the timer eventually gets a turn. Without a bound, repeatedly enqueueing microtasks can delay rendering and other tasks. In production code, that is a responsiveness problem, not a clever scheduling technique.

Does setTimeout(fn, 0) mean immediately?

No. It means "eligible after the current work and the relevant scheduling delay." The browser may clamp or delay timers; Node's event loop has its own phases. In an interview, avoid promising an exact millisecond. Explain the relative ordering that your test establishes.

How do you turn the code into an interview answer?

Use this four-step structure when the interviewer gives you a snippet.

  1. Name the current work. "The top-level statements run first."
  2. List queues by insertion order. "This queues a timer, then two microtasks."
  3. Apply the boundary. "When the current stack clears, microtasks drain before another task."
  4. State one caveat. "The relative order is the point; exact timer timing depends on the host."

Then pause. Do not race to define every event-loop term. If asked about browser rendering, I would add that rendering is host-controlled and should not be reduced to a blanket claim that it happens after every callback. If asked about Node, I would distinguish this basic timer-and-microtask example from Node-specific scheduling APIs and phases.

That restraint matters in senior interviews. It shows that you know where the general model ends.

A 15-minute practice routine

  • Minutes 1-3: Predict the first script without running it.
  • Minutes 4-6: Run it and make the assertion pass.
  • Minutes 7-10: Put a promise inside the first timer and write the new expected order.
  • Minutes 11-13: Explain the result aloud in four steps.
  • Minutes 14-15: Name one host-specific caveat you would check before shipping code.

Repeat with a different ordering tomorrow. The goal is not to recite a fixed answer; it is to build a reliable way to reason under a follow-up question.

For structured rehearsals that pair technical questions with answer feedback, aceround.app - AI interview assistant is one option to use after you have built your own explanation.

FAQ

Are promises always before timers?

For the ordinary case shown here, promise reactions placed in the microtask queue run after the current synchronous code and before a later timer task. Do not generalize that into a claim about every asynchronous API or every runtime phase.

Should I mention process.nextTick in a Node interview?

Only when asked, or when the code uses it. It has Node-specific behavior and can distract from the standard microtask-versus-timer explanation. First establish the portable model, then discuss Node's extra scheduling semantics.

Is this enough for a frontend interview?

It is the foundation. Pair it with one real scenario: stale search responses, an event handler that schedules work, or a rendering-performance issue. The interviewer wants to see that you can use the model to prevent a bug, not only recite its terminology.

References

Disclosure: This article was drafted with AI assistance and reviewed for technical accuracy, examples, and wording by the author.

Top comments (0)