Engrain by ByteRush

JS.01.02 Junior· 9 minCore — asked often

Tasks and the task queue: how the event loop picks what runs next

Timers, clicks and messages become tasks. The event loop runs one task at a time, which is why setTimeout(fn, 0) never means 0 ms.

Comes up in: JavaScript, Recruiter screen

Learn#

Arjun’s quiz app shows a countdown with setTimeout(tick, 1000). On a slow phone the “1 second” ticks take 1.3 seconds, and the timer drifts. Why does the browser not keep its promise?

Because a timer only says when a callback may run. Running it is the job of the event loop. The browser keeps a task queue: a waiting line of tasks. A task is one piece of work, such as running your script or one timer callback.

Tasks come from task sources: timers, network responses, user events like clicks, and messages sent with postMessage or a MessageChannel.

The loop repeats one rule: take the oldest ready task, run it until the stack is empty, then look again. One task per turn, never two at once.

setTimeout(() => console.log("timer 100 ms"), 100);
setTimeout(() => console.log("timer 0 ms"), 0);
setTimeout(() => {
  console.log("timer 50 ms starts");
  const t = Date.now();
  while (Date.now() - t < 100) {} // a slow task: 100 ms of work
  console.log("timer 50 ms ends");
}, 50);
console.log("script done");

Output (verified)

script done
timer 0 ms
timer 50 ms starts
timer 50 ms ends
timer 100 ms

The 100 ms timer ran around 150 ms, because a task was busy. A delay is a minimum, never an exact time.

One task at a timeStep 1 of 6

Call stack

  • script

Task queue

    empty

    Console

    • script done

    The script is the first task. It schedules three timers and prints.

    Read the steps as text
    1. The script is the first task. It schedules three timers and prints.
    2. The script ends. The 0 ms timer is ready, so its callback waits in the task queue.
    3. The event loop takes one task and runs it to the end.
    4. At 50 ms the next timer is ready. Its callback runs a slow loop for 100 ms.
    5. At 100 ms the last timer is ready, but a task is still running. It has to wait in the queue.
    6. Around 150 ms the slow task ends. Only then does the 100 ms timer run — about 50 ms late.

    Predict#

    Predict first

    What does this print? Type the letters with spaces.

    setTimeout(() => console.log("A"), 10);
    setTimeout(() => console.log("B"), 0);
    console.log("C");

    Interview angle#

    Say it like this — about 30 seconds

    “The event loop runs one task at a time. A task is a unit of work like the script, a timer callback or a click handler. Each one runs until the stack is empty. Between tasks the loop drains the microtask queue — promise callbacks — and the browser may render. So setTimeout(fn, 0) is not 0 ms: the callback waits for the current task, all microtasks, and any earlier tasks.”

    Stays on your device. Nothing is uploaded.
    Follow-up Why is setTimeout(fn, 0) not really 0 ms?
    It only makes the callback ready. It still waits for the current task to finish and for tasks ahead of it, and nested timers are raised to at least 4 ms.
    Follow-up What is the difference between a task and a microtask?
    Tasks run one per loop turn. Microtasks (promise callbacks) run right after the current task, and all of them run before the next task (JS.01.03).
    Follow-up How would you build a timer that does not drift?
    Do not count ticks. On each tick, compute the remaining time from Date.now() or performance.now() against a fixed end time.

    Practice#

    On your own

    Write measureDelay(ms): return a promise that resolves with the real time, in milliseconds, between calling it and the timer callback running.

    Done when

    • await measureDelay(30) is at least 30.
    • If the thread is busy for 80 ms right after the call, the result is at least 80.
    Your solution
    Ctrl/⌘ + Enter

    Remember#

    Try to answer each card before you open it. Then add them to your reviews.

    What does the event loop do on each turn?

    Takes the oldest ready task and runs it until the stack is empty.

    Name three task sources.

    Timers, user events like clicks, and network responses (also postMessage).

    Is a setTimeout delay exact?

    No. It is a minimum; the callback can run later.

    In 30 seconds: why is setTimeout(fn, 0) not really 0 ms?
    • It only makes the callback ready
    • It waits for the current task to finish
    • Microtasks and earlier tasks go first
    • Nested timers are clamped to 4 ms

    Go deeper

    Four layers. Stop at the one your interview needs; each ends with a quick check.

    The core ideaEvery round

    Tasks wait in a queue. The loop runs one, to the end, then looks for the next. A timer delay is the earliest time its callback may run, not a promise to run then.

    Check yourself: Two timers are ready. How many run in one loop turn?

    One. The next runs on a later turn.

    How it works underneathSenior rounds

    The HTML Standard says an event loop has several task queues, one or more per task source. Each turn, the browser picks a queue and runs the oldest task in it. Order is guaranteed within one source, not across sources — so browsers can serve clicks before timers when the page is busy Source: HTML Standard: Event loop processing model.

    Check yourself: Must the browser run tasks from different sources in the order they arrived?

    No. Order is kept within one source; the browser may choose which source to serve first.

    Edge cases and trapsWhere people slip

    Timer clamping: when a timer schedules a timer, and so on more than 5 levels deep, the browser raises delays under 4 ms to 4 ms Source: HTML Standard: Timers. Background tabs are throttled much harder, often to once per second or less. Node.js follows its own rules, not the HTML Standard (JS.01.06).

    Check yourself: When does the HTML Standard raise a timer's delay to 4 ms?

    When timers are nested more than 5 levels deep and the delay is under 4 ms.

    In productionStaff-level answers

    Clocks, countdowns and games must not trust tick counts. Store the end time and compute what is left on every tick. For anything visual, use requestAnimationFrame (JS.01.04). For long work, split it so other tasks — like the user’s next click — get a turn (JS.01.07).

    Check yourself: Why do countdowns built by counting ticks drift?

    Each tick can run late, and the delays add up.

    Finished the five steps? Mark it, and its cards join your reviews.

    Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

    Interview questions this answers

    • Explain the event loop. What is the difference between a task (macrotask) and a microtask?
    • Why is setTimeout(fn, 0) not really 0 ms?