cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Microtasks in depth

Predict the output of promise jobs, queueMicrotask callbacks, async await continuations, timers, and nested microtasks with real browser puzzle runs.

By the end you can
  • 01
    Read microtask producersSpot promise jobs, queueMicrotask callbacks, and await resumptions.
  • 02
    Predict output orderTrace sync code, microtasks, nested microtasks, and timer tasks.
  • 03
    Avoid microtask trapsUse microtasks for tiny follow-ups without starving the next task.

Microtasks are the event loop’s sticky notes

The previous lesson introduced the event loop: JavaScript runs one task at a time, and when the call stack is empty the host can pick the next task. This lesson zooms into the moment between tasks: the microtask checkpoint.

A microtask is tiny work that must run after the current synchronous code finishes but before the next task, such as a timer callback. Promise callbacks, queueMicrotask, and the continuation after await all use this timing.

Real-life analogySticky notes before the next meeting

Imagine a meeting ends. Before you leave your desk for the next meeting, you handle every sticky note already on the desk. If one sticky note creates another urgent sticky note, you handle that too. Only when the desk is clear do you walk to the next meeting.

In real life: Your current meeting
In JavaScript: The current task or script
In real life: Sticky notes on your desk
In JavaScript: Microtasks waiting to run
In real life: A note that says call Ada
In JavaScript: A promise job: call this .then callback
In real life: Leaving for the next meeting
In JavaScript: Taking the next task, like a timeout

Where the analogy stops: The browser is not literally using sticky notes, and rendering can involve host-specific details. The analogy is for ordering: finish the current task, clear the notes, then move on.

The rule we will use

After the current stack clears, JavaScript drains the microtask queue in order. It keeps draining if microtasks add more microtasks. Only then can the host move to the next task.

We will make only the promises-and-await claims needed for ordering. Full promise mechanics and async functions get their own lessons next: here, a settled promise queues a .then callback as a microtask, and await x pauses the async function so the rest resumes in a later microtask.

Promise jobs: callbacks from settled promises

PROMISE JOBS

A promise job is a microtask created by promise machinery. In the code below, Promise.resolve() is already fulfilled, but the .then callback still does not run immediately. JavaScript queues a job that says, “call this callback with the result.”

A promise job waits for sync codePop out in the code editor (opens in a new tab)JavaScript
console.log("sync A");Promise.resolve().then(() => console.log("promise job"));console.log("sync B");// sync A, sync B, promise job

That ordering is the first trick in almost every puzzle: the callback is ready, but it waits until the current script finishes. This is why a promise callback beats a timer but loses to ordinary synchronous lines.

Real-life analogyA promise job is a callback sticky note

A settled promise does not interrupt the line you are reading. It leaves a note: when the current work is done, call this callback with the value.

In real life: A note says, “call Sam with the answer”
In JavaScript: A .then reaction says, “call this callback with the value”
In real life: You finish your sentence first
In JavaScript: The current synchronous code runs to completion
In real life: Then you call Sam
In JavaScript: The promise job runs at the microtask checkpoint

Where the analogy stops: A promise also stores state and values. We are using only the ordering piece here; promise internals come later.

queueMicrotask: write your own sticky note

INTERACTIVE

queueMicrotask(fn) is the direct API: put fn in the microtask queue. It is useful when you want to finish the current call first, then do a tiny follow-up before timers, user events, or rendering get another chance.

Common microtask sources
ProducerWhat gets queuedRemember
Promise.resolve().then(fn)A promise reaction jobThe callback runs after the current stack clears.
queueMicrotask(fn)Your callback as a microtaskDirect and explicit; errors behave like uncaught exceptions.
await valueThe rest of the async functionThe function pauses, then resumes in a later microtask.
A microtask queuing another microtaskAnother item at the back of the same queueIt still runs before the next task.
setTimeout(fn, 0)A task, not a microtaskIt waits until the microtask queue is empty.

Promise jobs and queueMicrotask callbacks share one FIFO queue. “FIFO” means first in, first out: if the promise job is queued first, it runs first; if the direct microtask is queued first, it runs first.

Prove nested microtasks beat a ready timer
Nested microtask proofPop out in the code editor (opens in a new tab)JavaScript
console.log("task start");setTimeout(() => console.log("timeout"), 0);queueMicrotask(() => {  console.log("micro 1");  queueMicrotask(() => console.log("micro 2"));});Promise.resolve().then(() => console.log("promise"));console.log("task end");
Captured order
    Try it yourself

    Run it and watch the nested micro 2 line. If microtasks were only one pass, the timeout would appear first — but it does not.

    This uses real queueMicrotask, Promise.resolve().then, and setTimeout in your browser.
    Promise errors become rejected promises
    Throwing inside a promise jobPop out in the code editor (opens in a new tab)JavaScript
    Promise.resolve()  .then(() => {    console.log("then A");    throw new Error("boom");  })  .catch(() => console.log("caught rejection"));queueMicrotask(() => console.log("queued microtask"));console.log("sync");
    Captured order
      Try it yourself

      Click to run the promise side safely. A throw inside queueMicrotask would be reported like an uncaught exception, so this lesson explains it without intentionally crashing the page.

      The promise side is safe to run. The queueMicrotask error behavior is described rather than triggered.
      Error handling difference

      A throw inside a .then callback rejects the promise, so a later .catch can handle it. A throw inside a queueMicrotask callback is reported like an uncaught exception by the host. The demo runs the promise side because we do not intentionally crash the lesson page.

      await ordering: the bookmark

      AWAIT

      An async function starts synchronously. When it reaches await x, it places a bookmark, steps away, and the rest of the function continues in a later microtask. Modern engines have optimized this path, especially for native promises, so avoid counting exact “ticks.” Predict the output order from real runs, not folklore.

      Real-life analogyawait is a bookmark

      You can read a page up to a bookmark, answer the door, then come back and continue from exactly that place. await works like that for an async function.

      In real life: Read until the bookmark
      In JavaScript: Run the async function until await
      In real life: Close the book for a moment
      In JavaScript: Return control to the current script
      In real life: Resume from the bookmark
      In JavaScript: Continue the async function in a microtask

      Where the analogy stops: The analogy does not describe promise assimilation or engine internals. It only explains why code after await runs later.

      The key await shapePop out in the code editor (opens in a new tab)JavaScript
      async function demo() {  console.log("before await");  await null;  console.log("after await");}demo();console.log("outside");// before await, outside, after await

      The puzzle gallery has three await puzzles: a basic pause, two async functions interleaving, and a thenable object. A thenable is an object with a then method. Awaiting one adds promise assimilation behavior, so we state only the order proven by the real run in this lesson.

      Replay one puzzle as a queue model

      STEP THROUGH

      The player below is a labeled model of the queues for one medium puzzle. It is not an engine debugger. It uses the real final order, then shows the important queue events: schedule a promise job, schedule a direct microtask, schedule a timer task, drain microtasks, then run the timer.

      Replay one medium puzzle as queues
      Step 0 of 10Ready
      Your turn: follow the blue line

      This is a labeled model of the queues for the medium puzzle. It is recorded from the lesson’s real expected order, but it is not an engine debugger.

      Running in
      1. script
      Next: line 1
      Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
      Promise.resolve().then(() => {  console.log("B");  Promise.resolve().then(() => console.log("D"));});queueMicrotask(() => console.log("C"));setTimeout(() => console.log("E"), 0);console.log("F");
      CallStoreChangeResultRun = next line. Ran = already executed.
      Recent returnsNothing yet. Start with the blue line.
      A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

      Where you’ll use microtasks

      Most days you do not call queueMicrotask directly. You meet microtasks because promises and async functions are everywhere: data loading, animation setup, UI state updates, and test assertions. Knowing the order prevents “why did this log first?” bugs.

      A tiny batching patternPop out in the code editor (opens in a new tab)JavaScript
      let queued = false;const pending = []; export function scheduleMessage(message) {  pending.push(message);  if (queued) return;  queued = true;  queueMicrotask(() => {    queued = false;    console.log(pending.join(", "));    pending.length = 0;  });}

      This pattern lets several synchronous calls collect work, then flushes once before the next task. Keep the callback short. If the work might take noticeable time, split it into tasks instead so the page can respond.

      Common misconceptions

      “A resolved promise runs its then callback immediately.”

      No. The callback is queued as a microtask after the current stack clears.

      “setTimeout(fn, 0) means right now.”

      It means a future task. Microtasks queued by the current task run first.

      “The browser runs only the microtasks that were queued at first.”

      It keeps draining. A microtask queued by a microtask can still run before the next task.

      “queueMicrotask and Promise.resolve().then are identical.”

      They share ordering, but thrown errors are reported differently.

      “await always costs a fixed number of ticks.”

      Modern engines changed and optimized await behavior. Trust real ordering tests, not old tick-count rules.

      Practice: trace the sticky notes

      5 EXERCISES
      Exercise 1 · Warm-upSolve a promise job order

      Predict the output order, then type the three words in order.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      console.log("one");
      Promise.resolve().then(() => console.log("two"));
      console.log("three");

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 2 · PracticeMicrotask before timeout

        Predict why a microtask beats a 0ms timeout.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        setTimeout(() => console.log("timeout"), 0);
        queueMicrotask(() => console.log("micro"));
        console.log("sync");

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 3 · PracticePredict async/await interleaving

          Trace the bookmark: what prints before and after the pause?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          async function label() {
            console.log("A");
            await null;
            console.log("B");
          }
          label();
          console.log("C");

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 4 · PracticeSchedule tiny work with queueMicrotask

            Read the program and type what the timeout prints.

            Starter codePop out in the code editor (opens in a new tab)JavaScript
            const steps = [];
            steps.push("start");
            queueMicrotask(() => steps.push("microtask"));
            steps.push("end");
            setTimeout(() => console.log(steps.join(" -> ")), 0);

            Answer, then press Check. Spacing and letter case don’t matter.

              Exercise 5 · ChallengeFix a starvation risk

              Explain the bug, then compare with the solution. This one is not auto-checked because the safest answer is a design change: stop or yield.

              Starter codePop out in the code editor (opens in a new tab)JavaScript
              function riskyLoop() {
                queueMicrotask(riskyLoop);
              }
              riskyLoop();

                Quiz: check your understanding

                7 QUESTIONS
                Lesson quiz · 7 questionsScore: first tries count
                1. Question 1 of 7What is a promise job in this lesson?

                  Choose an answer to see the explanation.

                2. Question 2 of 7What does the promise and queueMicrotask snippet print?

                  Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                  console.log("A");
                  Promise.resolve().then(() => console.log("B"));
                  queueMicrotask(() => console.log("C"));
                  console.log("D");

                  Choose an answer to see the explanation.

                3. Question 3 of 7Which statement about queueMicrotask is most accurate?

                  Choose an answer to see the explanation.

                4. Question 4 of 7What does the basic await snippet print?

                  Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                  async function demo() {
                    console.log("start");
                    await null;
                    console.log("after");
                  }
                  demo();
                  console.log("end");

                  Choose an answer to see the explanation.

                5. Question 5 of 7A microtask queues another microtask, while a 0ms timeout is waiting. What happens?

                  Choose an answer to see the explanation.

                6. Question 6 of 7What does the promise error snippet print?

                  Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                  Promise.resolve()
                    .then(() => {
                      console.log("then");
                      throw new Error("boom");
                    })
                    .catch(() => console.log("catch"));
                  queueMicrotask(() => console.log("micro"));
                  console.log("sync");

                  Choose an answer to see the explanation.

                7. Question 7 of 7Why can an endless chain of microtasks be a problem?

                  Choose an answer to see the explanation.

                Key takeaways

                • Promise .then callbacks run as microtasks when the promise settles.
                • queueMicrotask queues a microtask directly; keep it tiny.
                • await runs code before the await now and resumes after it in a later microtask.
                • Microtasks drain before the next task, including microtasks queued by other microtasks.
                • Too many self-queuing microtasks can starve timers, input, and rendering.

                One-liner.
                A microtask is a sticky note JavaScript handles after the current stack clears and before the next task begins.

                Up next: Callbacks.

                CompleteFrontend Clear concepts. Working examples.