Back to JavaScript
JavaScript
easy
mid

How do you approach output prediction questions involving Promises and async behavior?

Classic 'what's the output' problems hinge on: microtask queue order (Promises before setTimeout), synchronous code first, async function continuations are microtasks, await suspends even when value is already resolved, Promise.resolve().then(...) doesn't immediately invoke. Trace stack → micro → macro and you'll predict any ordering.

5 min read·~12 min to think through

Mental model

  1. Synchronous code runs first until the call stack is empty.
  2. Microtasks (Promise .then/.catch/.finally, async function continuations after await, queueMicrotask) drain completely.
  3. One macrotask (setTimeout/setInterval/I/O) runs.
  4. (Browser) Render frame if needed.
  5. Microtasks drain again.
  6. Repeat.

Classic examples

1. Order

js
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");

Output: A, D, C, B

  • Sync first: A, D.
  • Microtask: C.
  • Macrotask: B.

2. await suspends even on resolved

js
async function f() {
  console.log("1");
  await null;
  console.log("2");
}
console.log("start");
f();
console.log("end");

Output: start, 1, end, 2

  • f() runs synchronously until the await.
  • The continuation after await schedules as a microtask.
  • Sync code finishes (end), then microtask runs (2).

3. Chained .then microtasks

js
Promise.resolve()
  .then(() => console.log("A"))
  .then(() => console.log("B"));

Promise.resolve()
  .then(() => console.log("C"))
  .then(() => console.log("D"));

Output: A, C, B, D

  • First .then of each Promise queues first round: A, C.
  • Each schedules the next: B, D queued.
  • All microtasks drain in queue order.

4. async + setTimeout interleave

js
async function f() {
  console.log("1");
  await Promise.resolve();
  console.log("2");
}
setTimeout(() => console.log("3"), 0);
f();
console.log("4");

Output: 1, 4, 2, 3

  • Synchronous part of f(): 1.
  • 4.
  • Microtask: 2.
  • Macrotask: 3.

5. for-await-of

js
async function f() {
  for (const n of [1, 2, 3]) {
    await new Promise(r => setTimeout(r, 100));
    console.log(n);
  }
}

Sequential — each await waits 100ms. Total ~300ms. Use Promise.all(arr.map(...)) for parallel.

6. forEach + async — does NOT wait

js
[1, 2, 3].forEach(async (n) => {
  await delay(100);
  console.log(n);
});
console.log("after");

Output: after, then 1/2/3 (in some order) forEach doesn't await its async callbacks. Use for..of or Promise.all(map).

7. process.nextTick vs Promise (Node)

js
setImmediate(() => console.log("immediate"));
Promise.resolve().then(() => console.log("promise"));
process.nextTick(() => console.log("tick"));
console.log("sync");

Node output: sync, tick, promise, immediate

  • nextTick fires before Promise microtasks within the same phase in Node.
  • Promise microtasks before macrotasks (setImmediate).

8. Promise resolution chaining

js
Promise.resolve(1)
  .then((v) => Promise.resolve(v + 1))
  .then(console.log);

Output: 2. Returning a Promise inside .then chains — the next .then gets the resolved value.

9. Throw vs reject

js
async function f() { throw new Error("x"); }
f().then(() => "ok", (e) => console.log(e.message));

Output: "x". throw in async = rejection.

Heuristics

  • Sync → microtask → macrotask → render → repeat.
  • await always suspends, even with already-resolved value.
  • forEach doesn't await.
  • Promise.resolve() + .then = microtask, not immediate.
  • nextTick before Promise microtasks in Node only.

Interview framing

"Trace through stack → microtask → macrotask. Sync code runs first; then microtasks (Promise continuations, async function resumes after await, queueMicrotask) drain fully; then one macrotask runs; then microtasks again. The two non-obvious facts: await always yields a microtask hop even when the value is already resolved, and forEach(async) doesn't wait for its callbacks. In Node, process.nextTick outranks Promise microtasks but both still beat setTimeout."

Follow-up questions

  • Why does await yield even for already-resolved values?
  • Why doesn't forEach wait for async callbacks?
  • Compare process.nextTick and Promise microtasks.

Common mistakes

  • Assuming setTimeout(0) runs before Promise.then.
  • Using forEach with async.
  • Sequential awaits for independent work.

Performance considerations

  • Microtask floods can block paint. Move heavy work to setTimeout or Workers.

Edge cases

  • Microtask starvation (infinite chain blocks render).
  • Node phases for setImmediate vs setTimeout(0).
  • Returning a Promise from .then auto-chains.

Real-world examples

  • Classic interview trick questions, debugging weird timing bugs.

Senior engineer discussion

Seniors trace ordering by mental model, not memorization, and identify forEach+async / sequential-awaits anti-patterns immediately.

Related questions