Engrain by ByteRush

JS.01.03 Junior· 10 minCore — asked often

Microtasks: why promise callbacks run before timers

Promise callbacks are microtasks. They all run right after the current task, before any timer — which explains most event-loop interview puzzles.

Comes up in: JavaScript, Recruiter screen, Coding

Learn#

Priya’s interviewer shows four lines with setTimeout and Promise.resolve().then, and asks for the order. Priya says the timer runs first, “because it was written first”. The interviewer smiles. It does not.

Next to the task queue there is a second line: the microtask queue. A microtask is a small callback that must run as soon as the current code ends.

Every .then callback is a microtask. The language spec calls them jobs.

The rule: when the call stack becomes empty, the browser runs a microtask checkpoint. It runs every microtask in the queue — including ones added during the checkpoint — before the next task, timer or paint.

queueMicrotask(fn) adds a microtask directly, without a promise.

console.log("A script");
setTimeout(() => console.log("E timer"), 0);
Promise.resolve().then(() => {
  console.log("C promise");
  queueMicrotask(() => console.log("D microtask"));
});
new Promise((resolve) => { console.log("B executor"); resolve(); });

Output (verified)

A script
B executor
C promise
D microtask
E timer

Notice B: the function you pass to new Promise runs immediately. Only the reactions wait.

Microtasks jump ahead of tasksStep 1 of 6

Call stack

  • script

Microtasks

    empty

    Task queue

    • E timer

    Console

    • A script

    The script prints A and hands a timer to the host. When it fires, E will wait in the task queue.

    Read the steps as text
    1. The script prints A and hands a timer to the host. When it fires, E will wait in the task queue.
    2. The promise is already resolved, so its callback C goes straight into the microtask queue.
    3. The promise executor runs right now, inside the script. B prints.
    4. The script ends. Before any task, every microtask runs. C prints and queues D.
    5. D was added during the checkpoint, so it runs in the same checkpoint.
    6. The microtask queue is empty. Now the next task, the timer, can run.

    Predict#

    Predict first

    What prints first? new Promise((r) => { console.log('X'); r(); }).then(() => console.log('Z')); console.log('Y');

    Interview angle#

    Say it like this — about 30 seconds

    “After each task, when the call stack is empty, the browser runs a microtask checkpoint. Promise reactions and queueMicrotask callbacks go there, and all of them run — even new ones added meanwhile — before the next task or paint. So a resolved promise’s .then always beats setTimeout(fn, 0). The promise executor itself is synchronous. And if microtasks keep adding microtasks, the page starves: no timers, clicks or rendering.”

    Stays on your device. Nothing is uploaded.
    Follow-up Is the promise executor synchronous or asynchronous?
    Synchronous. It runs inside new Promise(...). Only the .then, .catch and .finally callbacks are queued as microtasks.
    Follow-up What happens if a microtask keeps queueing new microtasks?
    The checkpoint never ends. Timers, input and rendering wait forever, and the page freezes like an infinite loop.
    Follow-up Where do MutationObserver callbacks run?
    As microtasks, so one callback receives a batch of DOM changes made during the current task.

    Practice#

    faded

    Build batch(fn) — the trick behind batched updates in UI libraries. Calls to add during one task are collected and passed to fn once, as an array, in a microtask.

    Done when

    • add(1); add(2); add(3) in one task calls fn([1, 2, 3]) once.
    • fn runs before a setTimeout(…, 0) that was scheduled earlier.
    • Calls in a later task start a new batch; no calls means no fn call.
    Your solution
    Ctrl/⌘ + Enter

    Remember#

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

    When does the microtask queue run?

    Whenever the call stack empties, before the next task, timer or paint.

    Is the function passed to new Promise run now or later?

    Now, synchronously. Only .then/.catch/.finally callbacks wait.

    What does this print?
    setTimeout(() => console.log("T"), 0);
    Promise.resolve().then(() => console.log("P"));
    console.log("S");
    S
    P
    T
    What is microtask starvation?

    Microtasks keep adding microtasks, so tasks and rendering never get a turn.

    Go deeper

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

    The core ideaEvery round

    Two queues. After each task — when the stack is empty — all microtasks run, then the next task. Promise callbacks and queueMicrotask are microtasks; timers, clicks and network events are tasks.

    Check yourself: A microtask adds another microtask. Does it run before the next timer?

    Yes. The checkpoint runs until the microtask queue is empty.

    How it works underneathSenior rounds

    ECMA-262 defines promise jobs and leaves running them to the host Source: ECMA-262: Jobs and host operations to enqueue jobs. The HTML Standard runs them at a microtask checkpoint, which happens whenever a callback returns and the stack is empty — so two click listeners each get their microtasks run in between Source: HTML Standard: Perform a microtask checkpoint.

    Check yourself: Does the checkpoint only happen at the end of a task?

    No. It also happens after each callback the browser calls, whenever the stack becomes empty.

    Edge cases and trapsWhere people slip

    Starvation in action — the timer waits until the chain stops:

    let n = 0;
    setTimeout(() => console.log("timer ran after", n, "microtasks"), 0);
    function again() { if (++n < 100000) queueMicrotask(again); }
    queueMicrotask(again);

    Output (verified)

    timer ran after 100000 microtasks

    With no stopping condition, the page would freeze. MutationObserver callbacks are also microtasks.

    Check yourself: Why does the timer below wait for all 100,000 microtasks?

    The checkpoint keeps running while microtasks keep arriving; the timer is a task.

    In productionStaff-level answers

    Frameworks use microtasks to batch work: Vue’s nextTick and many state libraries collect changes made in one task and apply them once, before paint. Keep microtasks short. Long or endless microtask chains block input as badly as a long loop, and they do not show up as separate tasks in performance tools.

    Check yourself: Why is a microtask a good place to flush batched updates?

    It runs after all synchronous changes in the task but before the browser paints.

    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

    • Is the promise executor synchronous or asynchronous?
    • What happens if a microtask keeps queueing new microtasks?