[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-weekly-javascript-event-loop":32,"code:js:true:1tar7sm":432,"code:js:true:ayevf1":433,"code:js:true:f9rp1h":434,"code:js:true:18kkwfv":435,"code:js:true:32jxhu":436,"code:js:true:5amieo":437,"code:js:true:tu27kx":438,"code:js:true:m9mdzj":439,"code:js:true:1o5uicn":440,"code:js:true:3qhnzd":441},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"id":33,"slug":34,"title":35,"subtitle":36,"excerpt":37,"coverUrl":38,"locale":13,"readingMinutes":39,"publishedAt":40,"viewCount":41,"likeCount":19,"commentCount":19,"author":42,"vertical":47,"topic":48,"tags":51,"_count":63,"playground":65,"body":67,"bodyMd":416,"seo":417,"translationGroupId":419,"thread":420,"assessments":422,"translations":428,"quiz":430},"019fe660-2335-7652-9e51-7c6f28871874","weekly-javascript-event-loop","JavaScript Event Loop Explained: The Complete Guide",null,"Learn how the JavaScript event loop really works — call stack, microtasks, and macrotasks — with worked examples, edge cases, and a cheat sheet.","\u002Fmedia\u002Fcovers\u002Fweekly-javascript-event-loop.png",13,"2026-07-13T09:36:33.464Z",29,{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},"javascript","JavaScript",[52,54,57,60],{"slug":49,"name":53,"color":36},"Javascript",{"slug":55,"name":56,"color":36},"webdev","Webdev",{"slug":58,"name":59,"color":36},"tutorial","Tutorial",{"slug":61,"name":62,"color":36},"programming","Programming",{"assessments":64},1,{"slug":34,"title":66},"the JavaScript event loop",{"blocks":68,"version":64},[69,73,78,81,90,93,96,111,114,117,120,126,129,132,135,138,141,148,151,154,157,160,164,167,171,175,178,181,185,188,191,194,198,201,204,208,211,214,217,220,223,226,229,232,235,238,241,244,247,251,254,257,261,264,268,271,274,278,281,284,292,295,298,301,304,307,310,313,317,320,323,326,330,333,337,340,343,346,349,384,388,391,399,402,405,408],{"id":70,"html":71,"type":72},"b1","\u003Cp>You&#39;ve written \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode> expecting it to run &quot;immediately.&quot; It didn&#39;t. A \u003Ccode>Promise.then()\u003C\u002Fcode> you scheduled a line later ran first, and somewhere a \u003Ccode>for\u003C\u002Fcode> loop of 50,000 iterations froze your UI for a full second despite every function being &quot;async.&quot; None of this is a bug. It&#39;s the JavaScript event loop doing exactly what it always does — you just haven&#39;t seen the mechanism yet.\u003C\u002Fp>","paragraph",{"id":74,"html":75,"text":76,"type":77,"level":31},"b2","What you&#39;ll learn","What you'll learn","heading",{"id":79,"html":80,"type":72},"b3","\u003Cp>By the end of this guide you&#39;ll be able to:\u003C\u002Fp>",{"id":82,"type":83,"items":84,"ordered":18},"b4","list",[85,86,87,88,89],"Explain, precisely, why microtasks (Promises) always run before macrotasks (\u003Ccode>setTimeout\u003C\u002Fcode>, \u003Ccode>setInterval\u003C\u002Fcode>) — even at a zero delay","Predict the exact console output order of any mix of synchronous code, \u003Ccode>setTimeout\u003C\u002Fcode>, and \u003Ccode>await\u003C\u002Fcode>","Diagnose a frozen UI as a blocked call stack, not a &quot;slow async function&quot;","Choose correctly between \u003Ccode>queueMicrotask\u003C\u002Fcode>, \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode>, and \u003Ccode>requestAnimationFrame\u003C\u002Fcode> for a given timing need","Avoid the two most common event-loop bugs: microtask starvation and accidental serial \u003Ccode>await\u003C\u002Fcode>s in a loop",{"id":91,"html":92,"type":72},"b5","\u003Cp>\u003Cstrong>Who this is for:\u003C\u002Fstrong> you&#39;ve written \u003Ccode>async\u003C\u002Fcode>\u002F\u003Ccode>await\u003C\u002Fcode> and used \u003Ccode>setTimeout\u003C\u002Fcode>, but you want the model that makes their interaction predictable instead of memorized.\u003C\u002Fp>",{"id":94,"html":95,"text":95,"type":77,"level":31},"b6","Contents",{"id":97,"type":83,"items":98,"ordered":18},"b7",[99,100,101,102,103,104,105,106,107,108,109,110],"\u003Ca href=\"#why-the-javascript-event-loop-exists\">Why the JavaScript event loop exists\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-three-lines-and-a-loop\">The mental model\u003C\u002Fa>","\u003Ca href=\"#stage-1-the-call-stack-and-blocking-code\">Stage 1: the call stack and blocking code\u003C\u002Fa>","\u003Ca href=\"#stage-2-web-apis-and-the-macrotask-queue\">Stage 2: Web APIs and the macrotask queue\u003C\u002Fa>","\u003Ca href=\"#stage-3-promises-and-the-microtask-queue\">Stage 3: Promises and the microtask queue\u003C\u002Fa>","\u003Ca href=\"#stage-4-asyncawait-is-sugar-not-magic\">Stage 4: async\u002Fawait is sugar, not magic\u003C\u002Fa>","\u003Ca href=\"#stage-5-rendering-and-nodes-extra-queues\">Stage 5: rendering, and Node&#39;s extra queues\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices-when-to-reach-for-what\">Best practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key takeaways\u003C\u002Fa>",{"id":112,"html":113,"text":113,"type":77,"level":31},"b8","Why the JavaScript event loop exists",{"id":115,"html":116,"type":72},"b9","\u003Cp>JavaScript runs on a single thread. One call stack, one thing executing at a time, no parallel function calls in the same realm. That&#39;s a deliberate design — it means you never need locks or mutexes to protect a shared variable — but it creates an obvious problem: how does a single-threaded language do anything concurrent, like waiting on a network response, without freezing the entire page while it waits?\u003C\u002Fp>",{"id":118,"html":119,"type":72},"b10","\u003Cp>Here&#39;s the naive expectation, and why it would be a disaster if JavaScript worked this way:\u003C\u002Fp>",{"id":121,"code":122,"type":123,"language":124,"highlight":125},"b11","console.log(\"start\");\nconst data = fetch(\"\u002Fapi\u002Fdata\"); \u002F\u002F if this blocked the thread...\nconsole.log(data);               \u002F\u002F ...nothing else could run until it resolved\nconsole.log(\"end\");","code","js",[],{"id":127,"html":128,"type":72},"b12","\u003Cp>If \u003Ccode>fetch\u003C\u002Fcode> blocked the thread the way \u003Ccode>readFileSync\u003C\u002Fcode> blocks in a script, every click, scroll, and animation would freeze for the entire round trip — and that&#39;s just one request among the images, timers, and user interactions a real page juggles at once.\u003C\u002Fp>",{"id":130,"html":131,"type":72},"b13","\u003Cp>The event loop is the fix. It lets the single thread hand off slow work (timers, network calls, I\u002FO) to the browser or Node runtime, keep executing other code meanwhile, and come back to handle the result when it&#39;s ready — all without ever running two pieces of your JavaScript at the same time. Concurrency without threads.\u003C\u002Fp>",{"id":133,"html":134,"text":134,"type":77,"level":31},"b14","The mental model: three lines and a loop",{"id":136,"html":137,"type":72},"b15","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> there is one call stack that must be empty before the event loop will pull anything new into it, and two queues feeding that stack — a microtask queue that always drains completely before the next macrotask, and a macrotask queue that runs one task per turn of the loop.\u003C\u002Fp>",{"id":139,"html":140,"type":72},"b16","\u003Cp>Picture four lanes:\u003C\u002Fp>",{"id":142,"type":83,"items":143,"ordered":17},"b17",[144,145,146,147],"\u003Cstrong>The call stack.\u003C\u002Fstrong> Where your code actually executes, one frame at a time, top to bottom. As long as there&#39;s a frame on it, nothing else runs — not a timer, not a promise callback, not a click handler.","\u003Cstrong>Web APIs \u002F runtime APIs.\u003C\u002Fstrong> Things the JavaScript engine doesn&#39;t implement itself — \u003Ccode>setTimeout\u003C\u002Fcode>, \u003Ccode>fetch\u003C\u002Fcode>, DOM events, file I\u002FO in Node. You hand them a callback and they run outside the JS thread, on their own schedule.","\u003Cstrong>The macrotask queue\u003C\u002Fstrong> (also called the &quot;task queue&quot; or &quot;callback queue&quot;). Where completed timers, I\u002FO callbacks, and UI events line up to be run, one per loop iteration.","\u003Cstrong>The microtask queue.\u003C\u002Fstrong> Where \u003Ccode>Promise\u003C\u002Fcode> reactions (\u003Ccode>.then\u003C\u002Fcode>, \u003Ccode>.catch\u003C\u002Fcode>, \u003Ccode>.finally\u003C\u002Fcode>, \u003Ccode>await\u003C\u002Fcode> continuations) and \u003Ccode>queueMicrotask\u003C\u002Fcode> callbacks line up.",{"id":149,"html":150,"type":72},"b18","\u003Cp>The loop&#39;s rule, stated precisely: \u003Cstrong>after the currently running script finishes and the call stack is empty, the engine drains the entire microtask queue — including any new microtasks added while draining — before it takes even one macrotask off the queue.\u003C\u002Fstrong> Then it renders (in a browser) if needed, runs exactly one macrotask, and repeats.\u003C\u002Fp>",{"id":152,"html":153,"type":72},"b19","\u003Cp>That one sentence explains every &quot;surprising&quot; ordering you&#39;ll see below. It&#39;s not a list of rules to memorize — it&#39;s one rule, applied consistently.\u003C\u002Fp>",{"id":155,"html":156,"text":156,"type":77,"level":31},"b20","Stage 1: the call stack and blocking code",{"id":158,"html":159,"type":72},"b21","\u003Cp>Before queues matter at all, internalize what &quot;blocking&quot; means. This runs top to bottom, no gaps:\u003C\u002Fp>",{"id":161,"code":162,"type":123,"language":124,"highlight":163},"b22","console.log(\"A\");\nconsole.log(\"B\");\nconsole.log(\"C\");\n\u002F\u002F Output: A, B, C — nothing else could have run between these lines",[],{"id":165,"html":166,"type":72},"b23","\u003Cp>The call stack must fully unwind — every function call return — before the engine even looks at either queue. This is why a heavy synchronous loop freezes everything, including animations and clicks, no matter how many \u003Ccode>async\u003C\u002Fcode> functions exist elsewhere in your code:\u003C\u002Fp>",{"id":168,"code":169,"type":123,"language":124,"highlight":170},"b24","function blockFor(ms) {\n  const end = Date.now() + ms;\n  while (Date.now() \u003C end) {} \u002F\u002F busy-wait — keeps the stack occupied\n}\n\nsetTimeout(() => console.log(\"timer fired\"), 0);\nblockFor(3000); \u002F\u002F the call stack is \"busy\" for 3 full seconds\nconsole.log(\"sync work done\");\n\u002F\u002F Output: \"sync work done\" first, then \"timer fired\" —\n\u002F\u002F the timer's callback can't run until the stack is empty, no matter its delay",[],{"id":172,"html":173,"type":174},"b25","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> the event loop never interrupts running code. \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode> does not mean &quot;run in 0ms&quot; — it means &quot;run as soon as the stack is empty and it&#39;s this task&#39;s turn,&quot; which could be milliseconds or seconds later if the stack stays busy.\u003C\u002Fp>\n","quote",{"id":176,"html":177,"text":177,"type":77,"level":31},"b26","Stage 2: Web APIs and the macrotask queue",{"id":179,"html":180,"type":72},"b27","\u003Cp>\u003Ccode>setTimeout\u003C\u002Fcode> doesn&#39;t run your callback itself — it registers it with the runtime&#39;s timer API and immediately returns, freeing the stack. The runtime waits out the delay, then places the callback in the macrotask queue, where it waits for its turn:\u003C\u002Fp>",{"id":182,"code":183,"type":123,"language":124,"highlight":184},"b28","console.log(\"1\");\nsetTimeout(() => console.log(\"2\"), 0);\nconsole.log(\"3\");\n\u002F\u002F Output: 1, 3, 2",[],{"id":186,"html":187,"type":72},"b29","\u003Cp>\u003Ccode>&quot;1&quot;\u003C\u002Fcode> and \u003Ccode>&quot;3&quot;\u003C\u002Fcode> run synchronously on the call stack. The \u003Ccode>setTimeout\u003C\u002Fcode> callback is handed to the Web API layer, timed out immediately (0ms), and dropped into the macrotask queue — but it can only run once the current script finishes and the stack is empty, which is after \u003Ccode>&quot;3&quot;\u003C\u002Fcode> has already printed.\u003C\u002Fp>",{"id":189,"html":190,"text":190,"type":77,"level":31},"b30","Stage 3: Promises and the microtask queue",{"id":192,"html":193,"type":72},"b31","\u003Cp>This is where most developers&#39; intuition breaks, because a \u003Ccode>Promise\u003C\u002Fcode> callback and a \u003Ccode>setTimeout\u003C\u002Fcode> callback look similar but are scheduled onto \u003Cstrong>different queues with different priority\u003C\u002Fstrong>:\u003C\u002Fp>",{"id":195,"code":196,"type":123,"language":124,"highlight":197},"b32","console.log(\"1\");\nsetTimeout(() => console.log(\"2 (macrotask)\"), 0);\nPromise.resolve().then(() => console.log(\"3 (microtask)\"));\nconsole.log(\"4\");\n\u002F\u002F Output: 1, 4, 3 (microtask), 2 (macrotask)",[],{"id":199,"html":200,"type":72},"b33","\u003Cp>\u003Ccode>&quot;1&quot;\u003C\u002Fcode> and \u003Ccode>&quot;4&quot;\u003C\u002Fcode> run synchronously. Then the stack is empty — and per the rule above, the engine drains \u003Cstrong>all\u003C\u002Fstrong> microtasks before touching the macrotask queue. \u003Ccode>Promise.resolve().then(...)\u003C\u002Fcode> queued a microtask, so it runs before the \u003Ccode>setTimeout\u003C\u002Fcode> callback, even though both were scheduled with a &quot;0ms&quot; wait and the \u003Ccode>setTimeout\u003C\u002Fcode> line came first.\u003C\u002Fp>",{"id":202,"html":203,"type":72},"b34","\u003Cp>Chained \u003Ccode>.then()\u003C\u002Fcode>s compound this, because each one queues a new microtask \u003Cem>while the microtask queue is still draining\u003C\u002Fem>:\u003C\u002Fp>",{"id":205,"code":206,"type":123,"language":124,"highlight":207},"b35","Promise.resolve()\n  .then(() => console.log(\"a\"))\n  .then(() => console.log(\"b\"))\n  .then(() => console.log(\"c\"));\nsetTimeout(() => console.log(\"timer\"), 0);\n\u002F\u002F Output: a, b, c, timer — all three microtasks drain before the one macrotask runs",[],{"id":209,"html":210,"type":174},"b36","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> the microtask queue isn&#39;t drained once — it&#39;s drained \u003Cem>until empty\u003C\u002Fem>, and a microtask callback is allowed to add more microtasks that also run in this same pass. A macrotask never gets a turn while microtasks keep arriving.\u003C\u002Fp>\n",{"id":212,"html":213,"type":72},"b37","\u003C!-- playground:start -->",{"id":215,"html":216,"text":216,"type":77,"level":31},"b38","🎮 Try it yourself",{"id":218,"html":219,"type":72},"b39","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-13-weekly-javascript-event-loop\u002Fplayground\u002F\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":221,"html":222,"type":72},"b40","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":224,"html":225,"type":72},"b41","\u003C!-- playground:end -->",{"id":227,"html":228,"type":72},"b42","\u003C!-- quiz:start -->",{"id":230,"html":231,"text":231,"type":77,"level":31},"b43","🧠 Test yourself",{"id":233,"html":234,"type":72},"b44","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-13-weekly-javascript-event-loop\">Take the 9-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":236,"html":237,"type":72},"b45","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":239,"html":240,"type":72},"b46","\u003C!-- quiz:end -->",{"id":242,"html":243,"text":243,"type":77,"level":31},"b47","Stage 4: async\u002Fawait is sugar, not magic",{"id":245,"html":246,"type":72},"b48","\u003Cp>\u003Ccode>async\u003C\u002Fcode>\u002F\u003Ccode>await\u003C\u002Fcode> doesn&#39;t introduce a new scheduling mechanism — it&#39;s syntax sugar over Promises and the same microtask queue you just saw. An \u003Ccode>async function\u003C\u002Fcode> always returns a Promise, and \u003Ccode>await\u003C\u002Fcode> is equivalent to attaching a \u003Ccode>.then()\u003C\u002Fcode> and pausing the function&#39;s own execution until it resolves — the pause is local to that function, not to the whole program:\u003C\u002Fp>",{"id":248,"code":249,"type":123,"language":124,"highlight":250},"b49","async function run() {\n  console.log(\"A\");\n  await null; \u002F\u002F yields here — the rest of `run` becomes a microtask continuation\n  console.log(\"B\");\n}\n\nconsole.log(\"start\");\nrun();\nconsole.log(\"end\");\n\u002F\u002F Output: start, A, end, B",[],{"id":252,"html":253,"type":72},"b50","\u003Cp>Walk it in order: \u003Ccode>run()\u003C\u002Fcode> executes synchronously until it hits \u003Ccode>await\u003C\u002Fcode>. \u003Ccode>&quot;A&quot;\u003C\u002Fcode> logs. The \u003Ccode>await\u003C\u002Fcode> schedules the rest of the function (\u003Ccode>console.log(&quot;B&quot;)\u003C\u002Fcode>) as a microtask and immediately returns control to the caller. \u003Ccode>&quot;end&quot;\u003C\u002Fcode> logs synchronously. Only then, with the stack empty, does the microtask queue drain and \u003Ccode>&quot;B&quot;\u003C\u002Fcode> prints.\u003C\u002Fp>",{"id":255,"html":256,"type":72},"b51","\u003Cp>This has a real performance consequence: \u003Ccode>await\u003C\u002Fcode>ing sequentially in a loop serializes work that could run concurrently.\u003C\u002Fp>",{"id":258,"code":259,"type":123,"language":124,"highlight":260},"b52","\u002F\u002F slow: each request waits for the previous one to fully resolve\nfor (const id of ids) {\n  const item = await fetchItem(id); \u002F\u002F blocks the loop's progress every iteration\n  results.push(item);\n}\n\n\u002F\u002F fast: all requests start immediately, the loop only waits once\nconst results = await Promise.all(ids.map((id) => fetchItem(id)));",[],{"id":262,"html":263,"type":72},"b53","\u003Cp>Both are valid \u003Ccode>async\u003C\u002Fcode>\u002F\u003Ccode>await\u003C\u002Fcode>, and both compile to the same microtask machinery — the difference is entirely about \u003Cem>when\u003C\u002Fem> you start each Promise, not about \u003Ccode>await\u003C\u002Fcode> itself. The same ordering rules govern cancellation too: see \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fyour-fetch-is-still-running-after-the-user-left-cmf\">how AbortController actually cancels an in-flight fetch\u003C\u002Fa> for what happens to a pending microtask when its Promise is aborted mid-flight.\u003C\u002Fp>",{"id":265,"html":266,"text":267,"type":77,"level":31},"b54","Stage 5: rendering, and Node&#39;s extra queues","Stage 5: rendering, and Node's extra queues",{"id":269,"html":270,"type":72},"b55","\u003Cp>In a \u003Cstrong>browser\u003C\u002Fstrong>, the engine typically renders a new frame between macrotasks, after the microtask queue is empty. That&#39;s why \u003Ccode>requestAnimationFrame\u003C\u002Fcode> feels smoother for visual updates than \u003Ccode>setTimeout(fn, 16)\u003C\u002Fcode>: it&#39;s synchronized to the display&#39;s actual paint cycle, not an arbitrary timer.\u003C\u002Fp>",{"id":272,"html":273,"type":72},"b56","\u003Cp>\u003Cstrong>Node.js\u003C\u002Fstrong> subdivides the macrotask queue into ordered phases (timers, pending callbacks, poll, check, close callbacks) and adds one more microtask-like queue that runs even before Promise microtasks: \u003Ccode>process.nextTick\u003C\u002Fcode>. Its callbacks drain completely before the Promise microtask queue on every pass:\u003C\u002Fp>",{"id":275,"code":276,"type":123,"language":124,"highlight":277},"b57","\u002F\u002F Node.js only\nPromise.resolve().then(() => console.log(\"promise microtask\"));\nprocess.nextTick(() => console.log(\"nextTick\"));\n\u002F\u002F Output: nextTick, promise microtask",[],{"id":279,"html":280,"type":72},"b58","\u003Cp>The practical takeaway: \u003Cstrong>in Node, \u003Ccode>process.nextTick\u003C\u002Fcode> jumps the entire queue\u003C\u002Fstrong>, which is powerful and easy to abuse (see the starvation gotcha below).\u003C\u002Fp>",{"id":282,"html":283,"text":283,"type":77,"level":31},"b59","Edge cases and gotchas",{"id":285,"type":83,"items":286,"ordered":18},"b60",[287,288,289,290,291],"\u003Cstrong>Microtask starvation.\u003C\u002Fstrong> The microtask queue must fully drain before any macrotask runs, so a microtask that keeps scheduling more microtasks (a runaway recursive \u003Ccode>.then()\u003C\u002Fcode> chain, or reckless \u003Ccode>process.nextTick\u003C\u002Fcode> recursion in Node) can prevent timers, I\u002FO, and rendering from ever getting a turn — a real production failure mode, not a theoretical one, and it throws no error.","\u003Cstrong>Unhandled Promise rejections are silent by default.\u003C\u002Fstrong> A rejected Promise with no \u003Ccode>.catch()\u003C\u002Fcode> doesn&#39;t throw synchronously — it fires an \u003Ccode>unhandledrejection\u003C\u002Fcode> event (browser) or \u003Ccode>unhandledRejection\u003C\u002Fcode> (Node) asynchronously. Wrap \u003Ccode>await\u003C\u002Fcode> in \u003Ccode>try\u003C\u002Fcode>\u002F\u003Ccode>catch\u003C\u002Fcode> or attach \u003Ccode>.catch()\u003C\u002Fcode>; don&#39;t rely on rejections to surface the way thrown errors do.","\u003Cstrong>\u003Ccode>await\u003C\u002Fcode> inside \u003Ccode>Array.prototype.forEach\u003C\u002Fcode> doesn&#39;t wait.\u003C\u002Fstrong> \u003Ccode>forEach\u003C\u002Fcode> discards its callback&#39;s return value, so \u003Ccode>await\u003C\u002Fcode>ing inside it doesn&#39;t pause the outer function — every iteration&#39;s async work fires with no ordering guarantee. Use \u003Ccode>for...of\u003C\u002Fcode> to run sequentially, or \u003Ccode>Promise.all(array.map(...))\u003C\u002Fcode> to run concurrently and wait for all.","\u003Cstrong>Node timers aren&#39;t identical to the browser&#39;s.\u003C\u002Fstrong> \u003Ccode>setImmediate\u003C\u002Fcode> exists only in Node and runs in the &quot;check&quot; phase, with ordering relative to \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode> that depends on context (top-level vs. inside an I\u002FO callback). Don&#39;t port timer-ordering assumptions between the two runtimes without testing.","\u003Cstrong>A blocked stack blocks \u003Cem>everything\u003C\u002Fem>.\u003C\u002Fstrong> A synchronous JSON parse of a huge payload or a dense synchronous loop freezes scrolling and input for as long as it runs — \u003Ccode>async\u003C\u002Fcode> elsewhere in the codebase doesn&#39;t help, because the freeze is on the stack, not the queues.",{"id":293,"html":294,"text":294,"type":77,"level":31},"b61","Best practices: when to reach for what",{"id":296,"html":297,"type":72},"b62","\u003Cp>\u003Cstrong>Reach for \u003Ccode>queueMicrotask\u003C\u002Fcode>\u003C\u002Fstrong> when you need a callback to run before the next macrotask without the overhead or semantics of a resolved Promise — rare, mostly library-internal scheduling.\u003C\u002Fp>",{"id":299,"html":300,"type":72},"b63","\u003Cp>\u003Cstrong>Reach for \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode>\u003C\u002Fstrong> when you deliberately want to defer past the current microtask queue and yield to the macrotask queue — for example, chunking a long synchronous loop so the UI can repaint and handle input between chunks.\u003C\u002Fp>",{"id":302,"html":303,"type":72},"b64","\u003Cp>\u003Cstrong>Reach for \u003Ccode>requestAnimationFrame\u003C\u002Fcode>\u003C\u002Fstrong> for anything visual — it&#39;s timed to the browser&#39;s paint cycle, avoiding both timer jank and wasted work on a hidden tab.\u003C\u002Fp>",{"id":305,"html":306,"type":72},"b65","\u003Cp>\u003Cstrong>Avoid\u003C\u002Fstrong> using timer ordering as a substitute for real synchronization — a \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode> &quot;hack&quot; to wait for a DOM update works by accident, not by contract. Use the actual completion signal instead: a \u003Ccode>Promise\u003C\u002Fcode>, an event, a \u003Ccode>MutationObserver\u003C\u002Fcode>.\u003C\u002Fp>",{"id":308,"html":309,"type":72},"b66","\u003Cp>\u003Cstrong>Chunk long synchronous work\u003C\u002Fstrong> rather than hoping \u003Ccode>async\u003C\u002Fcode> will make it non-blocking on its own — \u003Ccode>async\u003C\u002Fcode> only yields at \u003Ccode>await\u003C\u002Fcode> points; a tight synchronous loop inside an \u003Ccode>async function\u003C\u002Fcode> still blocks the stack for its full duration. The same instinct — don&#39;t make the user wait on a queue they can&#39;t see — is why \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fyour-save-button-waits-300ms-for-nothing-stop-it-4954\">optimistic UI updates\u003C\u002Fa> update the screen before the network Promise resolves instead of after.\u003C\u002Fp>",{"id":311,"html":312,"text":312,"type":77,"level":31},"b67","FAQ",{"id":314,"html":315,"text":315,"type":77,"level":316},"b68","Why does a Promise always run before setTimeout, even with a 0ms delay?",3,{"id":318,"html":319,"type":72},"b69","\u003Cp>Because they&#39;re scheduled onto different queues, and the event loop drains the microtask queue (Promises) completely before running a single macrotask (timers). This holds regardless of the timer&#39;s delay value — even \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode> waits behind every pending microtask.\u003C\u002Fp>",{"id":321,"html":322,"text":322,"type":77,"level":316},"b70","Does async\u002Fawait block the event loop?",{"id":324,"html":325,"type":72},"b71","\u003Cp>No — \u003Ccode>await\u003C\u002Fcode> yields control back to the event loop while the awaited Promise is pending, letting other code run. What \u003Cem>does\u003C\u002Fem> block the loop is synchronous code, including synchronous code inside an \u003Ccode>async function\u003C\u002Fcode>, before it reaches its first \u003Ccode>await\u003C\u002Fcode>.\u003C\u002Fp>",{"id":327,"html":328,"text":329,"type":77,"level":316},"b72","What&#39;s the difference between the microtask queue and the macrotask queue?","What's the difference between the microtask queue and the macrotask queue?",{"id":331,"html":332,"type":72},"b73","\u003Cp>The microtask queue holds Promise callbacks and \u003Ccode>queueMicrotask\u003C\u002Fcode> callbacks and is fully drained after every task, before the next macrotask runs. The macrotask queue holds timers, I\u002FO callbacks, and UI events, and yields exactly one task per loop iteration — leaving room for rendering and other macrotasks in between.\u003C\u002Fp>",{"id":334,"html":335,"text":336,"type":77,"level":316},"b74","Is Node.js&#39;s event loop the same as the browser&#39;s?","Is Node.js's event loop the same as the browser's?",{"id":338,"html":339,"type":72},"b75","\u003Cp>The core rule — stack first, then drain microtasks, then one macrotask — is the same. Node adds its own phase structure (timers, poll, check, and others) plus \u003Ccode>process.nextTick\u003C\u002Fcode>, which jumps ahead of Promise microtasks with no browser equivalent.\u003C\u002Fp>",{"id":341,"html":342,"text":342,"type":77,"level":316},"b76","Can I make the event loop run two things at once?",{"id":344,"html":345,"type":72},"b77","\u003Cp>No — JavaScript&#39;s event loop is single-threaded by design; that&#39;s what makes it safe without locks. True parallelism requires Web Workers (browser) or Worker Threads (Node), which run on separate threads with their own call stacks and communicate via message passing, not shared memory.\u003C\u002Fp>",{"id":347,"html":348,"text":348,"type":77,"level":31},"b78","Cheat sheet",{"id":350,"head":351,"rows":355,"type":383},"b79",[352,353,354],"Task","Code","Runs when",[356,360,363,367,371,375,379],[357,358,359],"Run after current sync code, before any timer","\u003Ccode>queueMicrotask(fn)\u003C\u002Fcode>","End of current microtask drain",[357,361,362],"\u003Ccode>Promise.resolve().then(fn)\u003C\u002Fcode>","Same queue as above",[364,365,366],"Defer past all pending microtasks","\u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode>","Next macrotask turn",[368,369,370],"Sync visual updates to paint","\u003Ccode>requestAnimationFrame(fn)\u003C\u002Fcode>","Just before next repaint",[372,373,374],"Node: jump ahead of everything, including Promises","\u003Ccode>process.nextTick(fn)\u003C\u002Fcode>","Before the microtask queue, same turn",[376,377,378],"Run N async calls concurrently","\u003Ccode>await Promise.all(items.map(fn))\u003C\u002Fcode>","All start immediately, one wait",[380,381,382],"Run N async calls sequentially (rare — usually a bug)","\u003Ccode>for (const x of items) await fn(x)\u003C\u002Fcode>","Each waits for the previous","table",{"id":385,"code":386,"type":123,"language":124,"highlight":387},"b80","\u002F\u002F The whole ordering rule as runnable proof:\nconsole.log(\"1: sync\");\n\nsetTimeout(() => console.log(\"5: macrotask (timer)\"), 0);\n\nPromise.resolve()\n  .then(() => console.log(\"3: microtask\"))\n  .then(() => console.log(\"4: microtask (chained)\"));\n\nqueueMicrotask(() => console.log(\"3.5: also a microtask\"));\n\nconsole.log(\"2: sync\");\n\n\u002F\u002F Output, every time, in every spec-compliant engine:\n\u002F\u002F 1: sync\n\u002F\u002F 2: sync\n\u002F\u002F 3: microtask\n\u002F\u002F 3.5: also a microtask   (order vs. 3 depends on registration order, not type)\n\u002F\u002F 4: microtask (chained)\n\u002F\u002F 5: macrotask (timer)",[],{"id":389,"html":390,"text":390,"type":77,"level":31},"b81","Key takeaways",{"id":392,"type":83,"items":393,"ordered":18},"b82",[394,395,396,397,398],"One call stack, two queues: the stack always runs to empty first, then the \u003Cstrong>entire\u003C\u002Fstrong> microtask queue drains, then exactly one macrotask runs, then repeat.","\u003Ccode>Promise\u003C\u002Fcode> callbacks and \u003Ccode>queueMicrotask\u003C\u002Fcode> are microtasks; \u003Ccode>setTimeout\u003C\u002Fcode>, \u003Ccode>setInterval\u003C\u002Fcode>, and I\u002FO callbacks are macrotasks — microtasks always win the race, regardless of timer delay.","\u003Ccode>await\u003C\u002Fcode> is sugar over \u003Ccode>.then()\u003C\u002Fcode> — it yields at the \u003Ccode>await\u003C\u002Fcode> point, but synchronous code before it still blocks the stack like anything else.","A frozen UI means a blocked call stack, not &quot;async code being slow&quot; — find the synchronous culprit, don&#39;t add more \u003Ccode>await\u003C\u002Fcode>s hoping it helps.","\u003Ccode>await\u003C\u002Fcode> in a loop is sequential by default; use \u003Ccode>Promise.all\u003C\u002Fcode> with \u003Ccode>.map()\u003C\u002Fcode> when the calls don&#39;t depend on each other.",{"id":400,"html":401,"type":72},"b83","\u003Cp>You now know why that \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode> lost the race, and why a busy \u003Ccode>while\u003C\u002Fcode> loop can freeze a page full of &quot;non-blocking&quot; async code: the event loop isn&#39;t magic, it&#39;s one rule about queues, applied consistently. Next time output surprises you, trace it against the rule instead of guessing. What&#39;s the strangest ordering bug the event loop has ever handed you? Drop it in the comments — there&#39;s a good chance it&#39;s stage 3 or stage 5 in disguise.\u003C\u002Fp>",{"id":403,"type":404},"b84","divider",{"id":406,"html":407,"type":72},"b85","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":409,"type":83,"items":410,"ordered":18},"b86",[411,412,413,414,415],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","You've written `setTimeout(fn, 0)` expecting it to run \"immediately.\" It didn't. A `Promise.then()` you scheduled a line later ran first, and somewhere a `for` loop of 50,000 iterations froze your UI for a full second despite every function being \"async.\" None of this is a bug. It's the JavaScript event loop doing exactly what it always does — you just haven't seen the mechanism yet.\n\n## What you'll learn\n\nBy the end of this guide you'll be able to:\n\n- Explain, precisely, why microtasks (Promises) always run before macrotasks (`setTimeout`, `setInterval`) — even at a zero delay\n- Predict the exact console output order of any mix of synchronous code, `setTimeout`, and `await`\n- Diagnose a frozen UI as a blocked call stack, not a \"slow async function\"\n- Choose correctly between `queueMicrotask`, `setTimeout(fn, 0)`, and `requestAnimationFrame` for a given timing need\n- Avoid the two most common event-loop bugs: microtask starvation and accidental serial `await`s in a loop\n\n**Who this is for:** you've written `async`\u002F`await` and used `setTimeout`, but you want the model that makes their interaction predictable instead of memorized.\n\n## Contents\n\n- [Why the JavaScript event loop exists](#why-the-javascript-event-loop-exists)\n- [The mental model](#the-mental-model-three-lines-and-a-loop)\n- [Stage 1: the call stack and blocking code](#stage-1-the-call-stack-and-blocking-code)\n- [Stage 2: Web APIs and the macrotask queue](#stage-2-web-apis-and-the-macrotask-queue)\n- [Stage 3: Promises and the microtask queue](#stage-3-promises-and-the-microtask-queue)\n- [Stage 4: async\u002Fawait is sugar, not magic](#stage-4-asyncawait-is-sugar-not-magic)\n- [Stage 5: rendering, and Node's extra queues](#stage-5-rendering-and-nodes-extra-queues)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices](#best-practices-when-to-reach-for-what)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n- [Key takeaways](#key-takeaways)\n\n## Why the JavaScript event loop exists\n\nJavaScript runs on a single thread. One call stack, one thing executing at a time, no parallel function calls in the same realm. That's a deliberate design — it means you never need locks or mutexes to protect a shared variable — but it creates an obvious problem: how does a single-threaded language do anything concurrent, like waiting on a network response, without freezing the entire page while it waits?\n\nHere's the naive expectation, and why it would be a disaster if JavaScript worked this way:\n\n```js\nconsole.log(\"start\");\nconst data = fetch(\"\u002Fapi\u002Fdata\"); \u002F\u002F if this blocked the thread...\nconsole.log(data);               \u002F\u002F ...nothing else could run until it resolved\nconsole.log(\"end\");\n```\n\nIf `fetch` blocked the thread the way `readFileSync` blocks in a script, every click, scroll, and animation would freeze for the entire round trip — and that's just one request among the images, timers, and user interactions a real page juggles at once.\n\nThe event loop is the fix. It lets the single thread hand off slow work (timers, network calls, I\u002FO) to the browser or Node runtime, keep executing other code meanwhile, and come back to handle the result when it's ready — all without ever running two pieces of your JavaScript at the same time. Concurrency without threads.\n\n## The mental model: three lines and a loop\n\n**The mental model:** there is one call stack that must be empty before the event loop will pull anything new into it, and two queues feeding that stack — a microtask queue that always drains completely before the next macrotask, and a macrotask queue that runs one task per turn of the loop.\n\nPicture four lanes:\n\n1. **The call stack.** Where your code actually executes, one frame at a time, top to bottom. As long as there's a frame on it, nothing else runs — not a timer, not a promise callback, not a click handler.\n2. **Web APIs \u002F runtime APIs.** Things the JavaScript engine doesn't implement itself — `setTimeout`, `fetch`, DOM events, file I\u002FO in Node. You hand them a callback and they run outside the JS thread, on their own schedule.\n3. **The macrotask queue** (also called the \"task queue\" or \"callback queue\"). Where completed timers, I\u002FO callbacks, and UI events line up to be run, one per loop iteration.\n4. **The microtask queue.** Where `Promise` reactions (`.then`, `.catch`, `.finally`, `await` continuations) and `queueMicrotask` callbacks line up.\n\nThe loop's rule, stated precisely: **after the currently running script finishes and the call stack is empty, the engine drains the entire microtask queue — including any new microtasks added while draining — before it takes even one macrotask off the queue.** Then it renders (in a browser) if needed, runs exactly one macrotask, and repeats.\n\nThat one sentence explains every \"surprising\" ordering you'll see below. It's not a list of rules to memorize — it's one rule, applied consistently.\n\n## Stage 1: the call stack and blocking code\n\nBefore queues matter at all, internalize what \"blocking\" means. This runs top to bottom, no gaps:\n\n```js\nconsole.log(\"A\");\nconsole.log(\"B\");\nconsole.log(\"C\");\n\u002F\u002F Output: A, B, C — nothing else could have run between these lines\n```\n\nThe call stack must fully unwind — every function call return — before the engine even looks at either queue. This is why a heavy synchronous loop freezes everything, including animations and clicks, no matter how many `async` functions exist elsewhere in your code:\n\n```js\nfunction blockFor(ms) {\n  const end = Date.now() + ms;\n  while (Date.now() \u003C end) {} \u002F\u002F busy-wait — keeps the stack occupied\n}\n\nsetTimeout(() => console.log(\"timer fired\"), 0);\nblockFor(3000); \u002F\u002F the call stack is \"busy\" for 3 full seconds\nconsole.log(\"sync work done\");\n\u002F\u002F Output: \"sync work done\" first, then \"timer fired\" —\n\u002F\u002F the timer's callback can't run until the stack is empty, no matter its delay\n```\n\n> **Key concept:** the event loop never interrupts running code. `setTimeout(fn, 0)` does not mean \"run in 0ms\" — it means \"run as soon as the stack is empty and it's this task's turn,\" which could be milliseconds or seconds later if the stack stays busy.\n\n## Stage 2: Web APIs and the macrotask queue\n\n`setTimeout` doesn't run your callback itself — it registers it with the runtime's timer API and immediately returns, freeing the stack. The runtime waits out the delay, then places the callback in the macrotask queue, where it waits for its turn:\n\n```js\nconsole.log(\"1\");\nsetTimeout(() => console.log(\"2\"), 0);\nconsole.log(\"3\");\n\u002F\u002F Output: 1, 3, 2\n```\n\n`\"1\"` and `\"3\"` run synchronously on the call stack. The `setTimeout` callback is handed to the Web API layer, timed out immediately (0ms), and dropped into the macrotask queue — but it can only run once the current script finishes and the stack is empty, which is after `\"3\"` has already printed.\n\n## Stage 3: Promises and the microtask queue\n\nThis is where most developers' intuition breaks, because a `Promise` callback and a `setTimeout` callback look similar but are scheduled onto **different queues with different priority**:\n\n```js\nconsole.log(\"1\");\nsetTimeout(() => console.log(\"2 (macrotask)\"), 0);\nPromise.resolve().then(() => console.log(\"3 (microtask)\"));\nconsole.log(\"4\");\n\u002F\u002F Output: 1, 4, 3 (microtask), 2 (macrotask)\n```\n\n`\"1\"` and `\"4\"` run synchronously. Then the stack is empty — and per the rule above, the engine drains **all** microtasks before touching the macrotask queue. `Promise.resolve().then(...)` queued a microtask, so it runs before the `setTimeout` callback, even though both were scheduled with a \"0ms\" wait and the `setTimeout` line came first.\n\nChained `.then()`s compound this, because each one queues a new microtask *while the microtask queue is still draining*:\n\n```js\nPromise.resolve()\n  .then(() => console.log(\"a\"))\n  .then(() => console.log(\"b\"))\n  .then(() => console.log(\"c\"));\nsetTimeout(() => console.log(\"timer\"), 0);\n\u002F\u002F Output: a, b, c, timer — all three microtasks drain before the one macrotask runs\n```\n\n> **Key concept:** the microtask queue isn't drained once — it's drained *until empty*, and a microtask callback is allowed to add more microtasks that also run in this same pass. A macrotask never gets a turn while microtasks keep arriving.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-13-weekly-javascript-event-loop\u002Fplayground\u002F)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 9-question quiz →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-13-weekly-javascript-event-loop)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n## Stage 4: async\u002Fawait is sugar, not magic\n\n`async`\u002F`await` doesn't introduce a new scheduling mechanism — it's syntax sugar over Promises and the same microtask queue you just saw. An `async function` always returns a Promise, and `await` is equivalent to attaching a `.then()` and pausing the function's own execution until it resolves — the pause is local to that function, not to the whole program:\n\n```js\nasync function run() {\n  console.log(\"A\");\n  await null; \u002F\u002F yields here — the rest of `run` becomes a microtask continuation\n  console.log(\"B\");\n}\n\nconsole.log(\"start\");\nrun();\nconsole.log(\"end\");\n\u002F\u002F Output: start, A, end, B\n```\n\nWalk it in order: `run()` executes synchronously until it hits `await`. `\"A\"` logs. The `await` schedules the rest of the function (`console.log(\"B\")`) as a microtask and immediately returns control to the caller. `\"end\"` logs synchronously. Only then, with the stack empty, does the microtask queue drain and `\"B\"` prints.\n\nThis has a real performance consequence: `await`ing sequentially in a loop serializes work that could run concurrently.\n\n```js\n\u002F\u002F slow: each request waits for the previous one to fully resolve\nfor (const id of ids) {\n  const item = await fetchItem(id); \u002F\u002F blocks the loop's progress every iteration\n  results.push(item);\n}\n\n\u002F\u002F fast: all requests start immediately, the loop only waits once\nconst results = await Promise.all(ids.map((id) => fetchItem(id)));\n```\n\nBoth are valid `async`\u002F`await`, and both compile to the same microtask machinery — the difference is entirely about *when* you start each Promise, not about `await` itself. The same ordering rules govern cancellation too: see [how AbortController actually cancels an in-flight fetch](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fyour-fetch-is-still-running-after-the-user-left-cmf) for what happens to a pending microtask when its Promise is aborted mid-flight.\n\n## Stage 5: rendering, and Node's extra queues\n\nIn a **browser**, the engine typically renders a new frame between macrotasks, after the microtask queue is empty. That's why `requestAnimationFrame` feels smoother for visual updates than `setTimeout(fn, 16)`: it's synchronized to the display's actual paint cycle, not an arbitrary timer.\n\n**Node.js** subdivides the macrotask queue into ordered phases (timers, pending callbacks, poll, check, close callbacks) and adds one more microtask-like queue that runs even before Promise microtasks: `process.nextTick`. Its callbacks drain completely before the Promise microtask queue on every pass:\n\n```js\n\u002F\u002F Node.js only\nPromise.resolve().then(() => console.log(\"promise microtask\"));\nprocess.nextTick(() => console.log(\"nextTick\"));\n\u002F\u002F Output: nextTick, promise microtask\n```\n\nThe practical takeaway: **in Node, `process.nextTick` jumps the entire queue**, which is powerful and easy to abuse (see the starvation gotcha below).\n\n## Edge cases and gotchas\n\n- **Microtask starvation.** The microtask queue must fully drain before any macrotask runs, so a microtask that keeps scheduling more microtasks (a runaway recursive `.then()` chain, or reckless `process.nextTick` recursion in Node) can prevent timers, I\u002FO, and rendering from ever getting a turn — a real production failure mode, not a theoretical one, and it throws no error.\n- **Unhandled Promise rejections are silent by default.** A rejected Promise with no `.catch()` doesn't throw synchronously — it fires an `unhandledrejection` event (browser) or `unhandledRejection` (Node) asynchronously. Wrap `await` in `try`\u002F`catch` or attach `.catch()`; don't rely on rejections to surface the way thrown errors do.\n- **`await` inside `Array.prototype.forEach` doesn't wait.** `forEach` discards its callback's return value, so `await`ing inside it doesn't pause the outer function — every iteration's async work fires with no ordering guarantee. Use `for...of` to run sequentially, or `Promise.all(array.map(...))` to run concurrently and wait for all.\n- **Node timers aren't identical to the browser's.** `setImmediate` exists only in Node and runs in the \"check\" phase, with ordering relative to `setTimeout(fn, 0)` that depends on context (top-level vs. inside an I\u002FO callback). Don't port timer-ordering assumptions between the two runtimes without testing.\n- **A blocked stack blocks *everything*.** A synchronous JSON parse of a huge payload or a dense synchronous loop freezes scrolling and input for as long as it runs — `async` elsewhere in the codebase doesn't help, because the freeze is on the stack, not the queues.\n\n## Best practices: when to reach for what\n\n**Reach for `queueMicrotask`** when you need a callback to run before the next macrotask without the overhead or semantics of a resolved Promise — rare, mostly library-internal scheduling.\n\n**Reach for `setTimeout(fn, 0)`** when you deliberately want to defer past the current microtask queue and yield to the macrotask queue — for example, chunking a long synchronous loop so the UI can repaint and handle input between chunks.\n\n**Reach for `requestAnimationFrame`** for anything visual — it's timed to the browser's paint cycle, avoiding both timer jank and wasted work on a hidden tab.\n\n**Avoid** using timer ordering as a substitute for real synchronization — a `setTimeout(fn, 0)` \"hack\" to wait for a DOM update works by accident, not by contract. Use the actual completion signal instead: a `Promise`, an event, a `MutationObserver`.\n\n**Chunk long synchronous work** rather than hoping `async` will make it non-blocking on its own — `async` only yields at `await` points; a tight synchronous loop inside an `async function` still blocks the stack for its full duration. The same instinct — don't make the user wait on a queue they can't see — is why [optimistic UI updates](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fyour-save-button-waits-300ms-for-nothing-stop-it-4954) update the screen before the network Promise resolves instead of after.\n\n## FAQ\n\n### Why does a Promise always run before setTimeout, even with a 0ms delay?\n\nBecause they're scheduled onto different queues, and the event loop drains the microtask queue (Promises) completely before running a single macrotask (timers). This holds regardless of the timer's delay value — even `setTimeout(fn, 0)` waits behind every pending microtask.\n\n### Does async\u002Fawait block the event loop?\n\nNo — `await` yields control back to the event loop while the awaited Promise is pending, letting other code run. What *does* block the loop is synchronous code, including synchronous code inside an `async function`, before it reaches its first `await`.\n\n### What's the difference between the microtask queue and the macrotask queue?\n\nThe microtask queue holds Promise callbacks and `queueMicrotask` callbacks and is fully drained after every task, before the next macrotask runs. The macrotask queue holds timers, I\u002FO callbacks, and UI events, and yields exactly one task per loop iteration — leaving room for rendering and other macrotasks in between.\n\n### Is Node.js's event loop the same as the browser's?\n\nThe core rule — stack first, then drain microtasks, then one macrotask — is the same. Node adds its own phase structure (timers, poll, check, and others) plus `process.nextTick`, which jumps ahead of Promise microtasks with no browser equivalent.\n\n### Can I make the event loop run two things at once?\n\nNo — JavaScript's event loop is single-threaded by design; that's what makes it safe without locks. True parallelism requires Web Workers (browser) or Worker Threads (Node), which run on separate threads with their own call stacks and communicate via message passing, not shared memory.\n\n## Cheat sheet\n\n| Task | Code | Runs when |\n| --- | --- | --- |\n| Run after current sync code, before any timer | `queueMicrotask(fn)` | End of current microtask drain |\n| Run after current sync code, before any timer | `Promise.resolve().then(fn)` | Same queue as above |\n| Defer past all pending microtasks | `setTimeout(fn, 0)` | Next macrotask turn |\n| Sync visual updates to paint | `requestAnimationFrame(fn)` | Just before next repaint |\n| Node: jump ahead of everything, including Promises | `process.nextTick(fn)` | Before the microtask queue, same turn |\n| Run N async calls concurrently | `await Promise.all(items.map(fn))` | All start immediately, one wait |\n| Run N async calls sequentially (rare — usually a bug) | `for (const x of items) await fn(x)` | Each waits for the previous |\n\n```js\n\u002F\u002F The whole ordering rule as runnable proof:\nconsole.log(\"1: sync\");\n\nsetTimeout(() => console.log(\"5: macrotask (timer)\"), 0);\n\nPromise.resolve()\n  .then(() => console.log(\"3: microtask\"))\n  .then(() => console.log(\"4: microtask (chained)\"));\n\nqueueMicrotask(() => console.log(\"3.5: also a microtask\"));\n\nconsole.log(\"2: sync\");\n\n\u002F\u002F Output, every time, in every spec-compliant engine:\n\u002F\u002F 1: sync\n\u002F\u002F 2: sync\n\u002F\u002F 3: microtask\n\u002F\u002F 3.5: also a microtask   (order vs. 3 depends on registration order, not type)\n\u002F\u002F 4: microtask (chained)\n\u002F\u002F 5: macrotask (timer)\n```\n\n## Key takeaways\n\n- One call stack, two queues: the stack always runs to empty first, then the **entire** microtask queue drains, then exactly one macrotask runs, then repeat.\n- `Promise` callbacks and `queueMicrotask` are microtasks; `setTimeout`, `setInterval`, and I\u002FO callbacks are macrotasks — microtasks always win the race, regardless of timer delay.\n- `await` is sugar over `.then()` — it yields at the `await` point, but synchronous code before it still blocks the stack like anything else.\n- A frozen UI means a blocked call stack, not \"async code being slow\" — find the synchronous culprit, don't add more `await`s hoping it helps.\n- `await` in a loop is sequential by default; use `Promise.all` with `.map()` when the calls don't depend on each other.\n\nYou now know why that `setTimeout(fn, 0)` lost the race, and why a busy `while` loop can freeze a page full of \"non-blocking\" async code: the event loop isn't magic, it's one rule about queues, applied consistently. Next time output surprises you, trace it against the rule instead of guessing. What's the strangest ordering bug the event loop has ever handed you? Drop it in the comments — there's a good chance it's stage 3 or stage 5 in disguise.\n\n---\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":35,"canonical":418,"description":37},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-javascript-event-loop","019fe660-2335-7652-9e51-821d306ab98e",{"id":421,"locked":18},"019fe660-2aa5-71ea-96e7-7eaf7c6a722d",[423],{"id":424,"slug":34,"title":425,"_count":426},"019fe776-44c6-72df-b609-831c84cffcf6","JavaScript Event Loop: Call Stack, Microtasks, and Macrotasks",{"questions":427},9,[429],{"locale":13,"slug":34},{"id":424,"slug":34,"title":425,"_count":431,"questionCount":427},{"questions":427},"\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"start\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> data \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"\u002Fapi\u002Fdata\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F if this blocked the thread...\n\u003C\u002Fspan>console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>data\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;               \u003Cspan class=\"shj-cmnt\">\u002F\u002F ...nothing else could run until it resolved\n\u003C\u002Fspan>console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"end\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"A\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"B\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"C\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Output: A, B, C — nothing else could have run between these lines\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">blockFor\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>ms\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> end \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Date\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">now\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">+\u003C\u002Fspan> ms;\n  \u003Cspan class=\"shj-kwd\">while\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-class\">Date\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">now\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan> end\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F busy-wait — keeps the stack occupied\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"timer fired\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-func\">blockFor\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">3000\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F the call stack is \"busy\" for 3 full seconds\n\u003C\u002Fspan>console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"sync work done\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Output: \"sync work done\" first, then \"timer fired\" —\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F the timer's callback can't run until the stack is empty, no matter its delay\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"1\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"2\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"3\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Output: 1, 3, 2\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"1\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"2 (macrotask)\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-class\">Promise\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">resolve\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"3 (microtask)\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"4\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Output: 1, 4, 3 (microtask), 2 (macrotask)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-class\">Promise\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">resolve\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"a\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"b\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"c\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"timer\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Output: a, b, c, timer — all three microtasks drain before the one macrotask runs\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">run\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"A\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F yields here — the rest of `run` becomes a microtask continuation\n\u003C\u002Fspan>  console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"B\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"start\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-func\">run\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"end\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Output: start, A, end, B\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F slow: each request waits for the previous one to fully resolve\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">for\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> id \u003Cspan class=\"shj-kwd\">of\u003C\u002Fspan> ids\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> item \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetchItem\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>id\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F blocks the loop's progress every iteration\n\u003C\u002Fspan>  results\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">push\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>item\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F fast: all requests start immediately, the loop only waits once\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> results \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Promise\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">all\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>ids\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">map\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>id\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetchItem\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>id\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F Node.js only\n\u003C\u002Fspan>\u003Cspan class=\"shj-class\">Promise\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">resolve\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"promise microtask\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nprocess\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">nextTick\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"nextTick\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Output: nextTick, promise microtask\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F The whole ordering rule as runnable proof:\n\u003C\u002Fspan>console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"1: sync\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"5: macrotask (timer)\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-class\">Promise\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">resolve\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"3: microtask\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"4: microtask (chained)\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-func\">queueMicrotask\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"3.5: also a microtask\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"2: sync\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Output, every time, in every spec-compliant engine:\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F 1: sync\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F 2: sync\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F 3: microtask\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F 3.5: also a microtask   (order vs. 3 depends on registration order, not type)\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F 4: microtask (chained)\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F 5: macrotask (timer)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>"]