- Which sequence is logged, and why?
Answer: 1, 2, 3, 5, 4; the executor is synchronous, but the fulfillment handler is a microtask
Whenever you see a question mixing Promises with surrounding synchronous code, your first instinct should be to mentally separate the call stack (synchronous tasks) from the microtask queue (where Promise callbacks live).
Here's how the code actually executes: console.log("1") runs immediately. Then the new Promise(...) constructor is called — critically, the executor function (the callback passed to the constructor) runs synchronously, right now, inline. So "2" logs, resolve() is called (which schedules the .then handler as a microtask but does not run it yet), then "3" logs — all before the Promise constructor even returns. Next, console.log("5") runs, completing the synchronous script. Only after the call stack is empty does the JavaScript engine drain the microtask queue, running the .then handler and logging "4". The result: 1, 2, 3, 5, 4 — confirming answer A.
B is wrong because it assumes resolve() immediately invokes the .then handler mid-execution. In reality, .then handlers are always deferred to the microtask queue, never called inline. C incorrectly treats the executor as deferred — it's not; only the handlers are async. D gets the order of 4 and 5 backwards, falsely claiming the .then handler fires before the surrounding synchronous code finishes.
A quick mental model to memorize: executor = sync, handlers = microtask. Any time you see a Promise constructor, the code inside it runs immediately; anything chained via .then waits until the current synchronous execution is done.
- Which statement most accurately describes the result?
Answer: inside and after call are logged; after await, the returned promise rejects rather than triggering that catch
When you call an async function, it begins executing synchronously until it hits the first await. At that point, it pauses, returns a pending Promise, and hands control back to the caller — this is the core concept being tested here.
In this code, failLater() runs synchronously through console.log("inside"), then hits await null and suspends. Control immediately returns to the try block, which then runs console.log("after call"). The try/catch block finishes and exits normally — the synchronous execution is complete. Later, in a microtask, the async function resumes, throws new Error("failure"), and that rejection ends up on the returned Promise. Because no .catch() or rejection handler was attached to that Promise, it becomes an unhandled rejection — it does not travel back to the original try/catch, which has already closed.
So the correct answer is C: inside and after call are logged, and the error becomes a rejected Promise rather than being caught by the surrounding try.
A is wrong because the try/catch block does not "stay open" waiting for the async function to finish. It exits as soon as the synchronous code completes. B is wrong because inside is logged — the async function body starts executing immediately and synchronously up to the first await. D is wrong because unhandled Promise rejections are not rethrown into the original call site; they emit an unhandledRejection event (or similar), but never reach that catch block.
As a study tip: whenever you see a try/catch wrapped around an async function call without await, remember — you're only catching synchronous errors from that call.
- Which statement best explains why Version 2 may keep the page more responsive?
Answer: Returning between batches gives the event loop opportunities to process input or rendering, although each batch still blocks while running
Whenever you see a question about JavaScript responsiveness and async patterns, anchor your thinking to the event loop: JavaScript runs on a single thread, and the event loop can only process one task at a time. Long synchronous operations block everything else — clicks, repaints, scroll events — until they finish.
Version 2's key insight is that setTimeout(fn, 0) doesn't run the next batch immediately — it schedules it as a new task at the end of the event queue. This means after each batch completes, the event loop gets a chance to handle any pending input events or rendering updates before picking up the next batch. That's exactly what C describes: returning between batches creates openings for the event loop, even though each individual batch still runs synchronously and blocks while it executes.
A is wrong because JavaScript does not spawn separate threads for timer callbacks. All timer callbacks execute on the same single thread as everything else — that's a foundational rule of the JavaScript runtime. Promises, setTimeout, and event handlers all funnel through the same event loop.
B is wrong because the computational work isn't removed — it's just spread out over multiple tasks. Every iteration still executes; nothing is skipped or eliminated.
D is wrong because a zero-delay timer makes no such guarantee. A setTimeout(..., 0) only promises the callback won't run before the delay; it doesn't guarantee it runs before other pending work. Rendering and input events may be processed first.
A good study tip: when evaluating async patterns, always trace what the event loop does between tasks — that's where responsiveness lives.
- What can be concluded about which callback executes first?
Answer: The order cannot be inferred solely from readiness or registration order across the different task sources
When you see a question about JavaScript's event loop and task scheduling, focus on how browsers actually prioritize work — and the answer may surprise you: the HTML specification defines the concept of task sources but does not mandate a strict priority ordering between them.
The event loop processes one task per iteration, selecting from task queues associated with different task sources (timers, networking, user input, etc.). The spec allows browsers to choose which task queue to service next, meaning the runtime has discretion. When two callbacks from different task sources are both ready and no other constraints apply, no rule forces one ahead of the other. That's exactly the scenario described here, making D the correct conclusion — you simply cannot infer the order.
A is wrong because the spec assigns no blanket "timers beat I/O" priority rule. Browsers may implement priority heuristics internally, but those are implementation details, not guaranteed behavior you can rely on. B falls for the same trap in reverse — completed I/O has no spec-mandated priority over timer callbacks. Neither source outranks the other universally. C is tempting because registration order does matter within a single task source (tasks in the same queue are processed FIFO), but that ordering guarantee does not extend across different task sources. Mixing sources breaks the FIFO guarantee entirely.
A useful pattern to remember: microtask ordering is strict and well-defined (always drain before the next task), but macrotask ordering across different sources is browser-discretionary. When a question involves multiple task sources with no explicit priority rule given, the safe answer is almost always that the order is unspecified.
- Which sequence is logged?
Answer: start, end, micro1, micro1b, timer1, micro2, timer3, timer2
Whenever you see JavaScript output-ordering questions, your mental model needs to be: synchronous code first, then microtasks, then macrotasks (timers) — and crucially, microtasks drain completely after every macrotask, not just once at the end.
Here's how to trace this code. Synchronous execution runs first: "start" logs, then "end" logs. During this phase, timer1's callback, timer3's callback, and the Promise .then are registered — not executed. The microtask queue now has the Promise callback; the macrotask queue has [timer1-callback, timer3-callback] in registration order.
Before any timer fires, the microtask queue drains: "micro1" logs. Inside that callback, queueMicrotask adds "micro1b" to the microtask queue, and a new setTimeout registers timer2. Since "micro1b" is queued before the queue is empty, it runs next — logging "micro1b". Now microtasks are exhausted.
The event loop picks up timer1-callback (the first macrotask): "timer1" logs, then a Promise resolves, queueing "micro2". Microtasks drain immediately after this macrotask: "micro2" logs. Next macrotask is timer3-callback: "timer3" logs. Finally timer2-callback: "timer2" logs. This gives you A: start, end, micro1, micro1b, timer1, micro2, timer3, timer2.
Choice B is wrong because it places "micro1b" after "timer1", as if queueMicrotask doesn't run before the next macrotask. Choice C incorrectly places "micro2" after both timers fire. Choice D ignores microtask priority entirely, running timers before the Promise resolves.
Your study tip: after every macrotask, mentally ask "is the microtask queue empty?" — microtasks always jump the timer queue, and they chain within the same flush.
- Which sequence is logged?
Answer: A, B, F, C, E, D
Whenever you see a question mixing async/await, setTimeout, and synchronous code, you need to think in three layers: the call stack (synchronous), the microtask queue (Promises/await), and the macrotask queue (setTimeout). Microtasks always drain completely before the next macrotask runs.
Here's the step-by-step for the correct answer, B (A, B, F, C, E, D). First, console.log("A") runs synchronously. Then setTimeout("E", 0) queues E as a macrotask. Then run() is called — console.log("B") runs synchronously inside it. The await Promise.resolve() suspends run(), scheduling the rest of it as a microtask. Control returns to the call stack, so console.log("F") runs next. Now the call stack is empty: the microtask queue drains first, printing C, and then setTimeout("D", 0) is registered as a macrotask. Finally, macrotasks fire in registration order — E was registered before D, so you get E, then D.
Answer A is wrong because it places F after C — this ignores that await yields control back to the call stack before the microtask resolves. Answer C places E before C, wrongly treating the macrotask as running before the microtask queue clears. Answer D puts F before B, which would only be true if run() were called after console.log("F").
Your mental model to remember: synchronous → microtasks → macrotasks, and await always suspends the async function immediately, letting synchronous code finish first.
- What is the most likely result after the initial script completes?
Answer: The timer may be starved because draining one microtask continually adds another microtask to the queue
When you see JavaScript questions involving queueMicrotask, setTimeout, and recursion together, you should immediately think about the event loop's priority system: microtasks always drain completely before the engine picks up the next macrotask (like a timer callback).
Here's what happens step-by-step: the synchronous script runs, calling continueForever(), which queues a microtask. Then setTimeout registers the timer callback, and console.log("script done") executes. When the call stack empties, the engine checks the microtask queue — but each time it processes continueForever, that function immediately queues another continueForever. The microtask queue never reaches zero, so the event loop never advances to pull the timer callback from the macrotask queue. The timer is effectively starved, making B correct.
A is wrong because it describes a behavior the event loop doesn't have — it does not alternate between microtasks and macrotasks. The rule is: drain all microtasks first, then process one macrotask, then drain microtasks again. There is no alternating.
C is wrong because registration order doesn't determine execution order across queue types. Macrotasks never "jump ahead" of pending microtasks, regardless of when they were registered.
D is a tempting trap — but notice the recursion happens via queueMicrotask, not direct function calls. Each invocation of continueForever returns before the next one runs, so the call stack stays shallow. There is no stack overflow.
As a study tip: whenever you see queueMicrotask in a loop or recursion, ask yourself "does this queue ever empty?" — if not, any setTimeout below it will never fire.
- Assuming the timer has not otherwise been selected for execution, what is logged?
Answer: timer scheduled, then timer cleared; the microtask cancels the timer before its task executes
When you see JavaScript code mixing setTimeout, Promises, and synchronous statements, you're being tested on the event loop — specifically the distinction between the call stack, microtask queue, and macrotask queue.
Here's how execution unfolds: JavaScript runs synchronously first. setTimeout registers a timer callback as a macrotask (even with a 0ms delay), then Promise.resolve().then(...) queues its callback as a microtask, and finally "timer scheduled" logs immediately. That's your first output. After the synchronous script finishes, the engine drains the microtask queue before picking up any macrotask. The Promise callback runs next — it calls clearTimeout(timerId), which cancels the pending timer, then logs "timer cleared". Now the macrotask queue is checked, but the timer was cancelled, so nothing fires. Final output: "timer scheduled", then "timer cleared" — confirming D.
A is wrong because clearTimeout genuinely cancels the timer; it doesn't merely delay it. Once cancelled, the callback never runs. B gets the queue order backwards — macrotasks do not run before microtasks that were queued during the same turn of the event loop. C describes an impossible behavior: Promise callbacks never interrupt a running script. They only execute after the current call stack is empty, so "timer scheduled" always logs before "timer cleared".
As a study tip, remember this priority order: synchronous code → microtasks (Promises) → macrotasks (setTimeout, setInterval). This ordering is the key to almost every event loop question you'll encounter.
- Assuming the requests do not depend on each other, which statement most accurately compares the functions?
Answer: loadTogether can overlap the requests, while their JavaScript continuations still execute one at a time on the event loop
When you see a question comparing async patterns in JavaScript, the key concept to focus on is the difference between when I/O is initiated and when JavaScript code executes.
In loadSequentially, the await before the first fetch pauses execution until that response arrives — only then does the second fetch call even get made. The requests run back-to-back. In loadTogether, both fetch calls are made before any awaiting happens, so the browser can issue both network requests simultaneously. Promise.all then waits for both to resolve. This is why A is correct: the I/O overlaps in time, but JavaScript's single-threaded event loop still processes each fulfillment callback one at a time — never truly in parallel at the code level.
B is wrong because Promise.all does not create any additional threads. JavaScript in the browser runs on a single thread; Promise.all is purely a coordination mechanism that tracks when multiple promises settle, nothing more.
C misrepresents how await works in loadSequentially. When you await an expression, execution suspends at that line and resumes only after the promise settles. The second fetch call is never reached until the first completes, so no overlap occurs.
D is a tempting distractor because fetch is indeed asynchronous — but in loadSequentially, the call itself to the second fetch doesn't happen until after the first await resolves. Asynchronous doesn't mean both calls are issued at the same moment.
Study tip: Distinguish between initiating I/O (when fetch is called) and waiting for it (when await is used). Overlapping requests requires issuing them before any await.
- Which outcome best describes the execution?
Answer: begin, end, microtask, and timer; both callbacks wait, then the microtask runs first
Whenever you see a question mixing setTimeout, Promises, and synchronous code, you need to think about JavaScript's event loop and the distinction between the call stack, the microtask queue, and the macrotask queue.
Here's the key mental model: JavaScript is single-threaded. Nothing asynchronous runs until the call stack is completely empty. busyWait(200) is synchronous — it blocks the thread entirely for 200 ms. Even though the setTimeout fires after ~10 ms and the Promise is resolved immediately, neither callback can execute while synchronous code is still running. So the order of synchronous execution is "begin", then the timer and microtask are scheduled (not run), then the busy-wait loop runs to completion, then "end" is logged. Only after "end" does the call stack clear.
Once the stack is empty, the event loop checks the microtask queue first before processing any macrotasks. Promise.resolve().then(...) queues a microtask, while setTimeout queues a macrotask. So "microtask" runs before "timer", even though the timer's delay expired long ago. This confirms C as the correct answer: begin, end, microtask, timer.
A is wrong because nothing can "interrupt" a running synchronous loop — JavaScript has no preemptive multitasking. B is wrong for the same reason; callbacks never run while the loop continues. D is wrong because it reverses the microtask/macrotask priority — an overdue timer does not jump ahead of queued microtasks.
Your study tip: always mentally drain the call stack first, then flush all microtasks, then handle the next macrotask. That sequence never changes.