[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-queuemicrotask-microtask-vs-macrotask":44,"search-suggestions":60,"quiz-article-queuemicrotask-microtask-vs-macrotask":109,"path-membership-queuemicrotask-microtask-vs-macrotask":299},[4,20,32],{"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","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","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.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"01a1a52e-126b-7259-8b0a-7ba01c0dcea4","queuemicrotask-microtask-vs-macrotask","PRACTICE_QUIZ","queueMicrotask vs setTimeout: test yourself","Seven questions on microtask vs macrotask ordering, queueMicrotask's API, and the gotchas that trip people up in tests and libraries.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},7,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,85,89,93,97,101,105],{"slug":62,"name":63,"articles":64},"webdev","Webdev",174,{"slug":66,"name":67,"articles":68},"javascript","Javascript",141,{"slug":70,"name":71,"articles":72},"frontend","Frontend",96,{"slug":74,"name":75,"articles":76},"tutorial","Tutorial",71,{"slug":78,"name":79,"articles":80},"css","Css",51,{"slug":82,"name":83,"articles":84},"performance","Performance",30,{"slug":86,"name":87,"articles":88},"typescript","Typescript",25,{"slug":90,"name":91,"articles":92},"react","React",20,{"slug":94,"name":95,"articles":96},"programming","Programming",19,{"slug":98,"name":99,"articles":100},"browser","Browser",18,{"slug":102,"name":103,"articles":104},"node","Node",16,{"slug":106,"name":107,"articles":108},"html","Html",12,{"id":110,"slug":46,"title":111,"subtitle":52,"excerpt":112,"coverUrl":113,"locale":13,"readingMinutes":114,"publishedAt":115,"viewCount":51,"likeCount":19,"commentCount":19,"author":116,"vertical":121,"topic":122,"tags":124,"_count":129,"playground":131,"body":133,"bodyMd":285,"seo":286,"translationGroupId":289,"series":52,"podcastUrl":52,"verticalId":5,"thread":290,"assessments":292,"translations":295,"quiz":297},"01a1a52e-1213-71ac-8458-b4edff92b4c2","Your `setTimeout(fn, 0)` Doesn't Run Next. `queueMicrotask` Does.","setTimeout(fn, 0) feels like 'run this right after the current code.' It isn't — every pending promise cuts in line first. queueMicrotask puts your callback in that same fast lane, on purpose.","\u002Fmedia\u002Fcovers\u002Fqueuemicrotask-microtask-vs-macrotask.png",5,"2026-11-10T14:37:38.531Z",{"id":117,"name":118,"username":119,"avatarUrl":52,"headline":120},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":66,"name":123},"JavaScript",[125,126,127,128],{"slug":66,"name":67,"color":52},{"slug":62,"name":63,"color":52},{"slug":102,"name":103,"color":52},{"slug":94,"name":95,"color":52},{"assessments":130},1,{"slug":46,"title":132},"queueMicrotask vs setTimeout — the race",{"blocks":134,"version":130},[135,139,142,145,149,152,158,161,165,168,171,174,180,183,186,190,193,196,199,202,205,208,212,215,219,222,225,228,231,234,237,240,243,246,249,252,255,258,261,267,270,273,276,279],{"id":136,"html":137,"type":138},"b1","\u003Cp>The test called the function, then did \u003Ccode>await Promise.resolve()\u003C\u002Fcode>, then checked the state. It failed. Not flaky — failed every single run, for this one function and no other.\u003C\u002Fp>","paragraph",{"id":140,"html":141,"type":138},"b2","\u003Cp>The function wasn&#39;t broken. It deferred its work with \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode>, and \u003Ccode>await Promise.resolve()\u003C\u002Fcode> doesn&#39;t wait for that. It can&#39;t. By the time \u003Ccode>setTimeout\u003C\u002Fcode> even gets a turn, that \u003Ccode>await\u003C\u002Fcode> is long finished.\u003C\u002Fp>",{"id":143,"html":144,"type":138},"b3","\u003Cp>The fix was a one-word swap. Understanding why it mattered took longer.\u003C\u002Fp>",{"id":146,"html":147,"text":147,"type":148,"level":31},"b4","The batcher that looked obviously right","heading",{"id":150,"html":151,"type":138},"b5","\u003Cp>Here&#39;s the function in question — a tiny update batcher for a settings panel. Call it five times in a row and it should write once, not five times:\u003C\u002Fp>",{"id":153,"code":154,"type":155,"language":156,"highlight":157},"b6","let pending = new Map();\nlet scheduled = false;\n\nfunction queueUpdate(key, value) {\n  pending.set(key, value);\n  if (!scheduled) {\n    scheduled = true;\n    setTimeout(flush, 0); \u002F\u002F \"run this once, right after the current burst of calls\"\n  }\n}\n\nfunction flush() {\n  scheduled = false;\n  applyUpdates(pending);\n  pending.clear();\n}","code","js",[],{"id":159,"html":160,"type":138},"b7","\u003Cp>Click a few toggles fast, and \u003Ccode>flush\u003C\u002Fcode> runs once with the final values. Manual testing looks perfect. Then someone writes the obvious test:\u003C\u002Fp>",{"id":162,"code":163,"type":155,"language":156,"highlight":164},"b8","test(\"coalesces rapid updates into one flush\", async () => {\n  queueUpdate(\"theme\", \"dark\");\n  queueUpdate(\"theme\", \"darker\");\n\n  await Promise.resolve(); \u002F\u002F \"let things settle\"\n\n  expect(getStoreValue(\"theme\")).toBe(\"darker\"); \u002F\u002F fails — still unset\n});",[],{"id":166,"html":167,"type":138},"b9","\u003Cp>That \u003Ccode>await Promise.resolve()\u003C\u002Fcode> is a common idiom for &quot;let the microtask queue drain before I assert.&quot; It isn&#39;t broken, and it isn&#39;t a flake you can retry away. \u003Ccode>flush\u003C\u002Fcode> genuinely has not run yet when the assertion checks — because \u003Ccode>setTimeout\u003C\u002Fcode>, even at \u003Ccode>0\u003C\u002Fcode>, schedules into a queue that a microtask-flushing \u003Ccode>await\u003C\u002Fcode> never visits.\u003C\u002Fp>",{"id":169,"html":170,"text":170,"type":148,"level":31},"b10","Two queues, and one of them always finishes first",{"id":172,"html":173,"type":138},"b11","\u003Cp>Every JavaScript engine actually runs two separate queues, and they are not interchangeable:\u003C\u002Fp>",{"id":175,"type":176,"items":177,"ordered":18},"b12","list",[178,179],"\u003Cstrong>The microtask queue\u003C\u002Fstrong> — where \u003Ccode>Promise.resolve().then(fn)\u003C\u002Fcode> and \u003Ccode>queueMicrotask(fn)\u003C\u002Fcode> both land, in the same queue.","\u003Cstrong>The macrotask (task) queue\u003C\u002Fstrong> — where \u003Ccode>setTimeout\u003C\u002Fcode>, UI events, and I\u002FO callbacks land.",{"id":181,"html":182,"type":138},"b13","\u003Cp>The HTML spec&#39;s event loop rule is blunt about the order: once the current script finishes, the engine drains the \u003Cem>entire\u003C\u002Fem> microtask queue — including any new microtasks queued while draining — before it&#39;s allowed to touch the next macrotask, or render a frame. This isn&#39;t &quot;promises happen to be fast.&quot; It&#39;s a hard ordering guarantee, and every major engine (V8, SpiderMonkey, JavaScriptCore) implements it the same way.\u003C\u002Fp>",{"id":184,"html":185,"type":138},"b14","\u003Cp>The classic proof:\u003C\u002Fp>",{"id":187,"code":188,"type":155,"language":156,"highlight":189},"b15","console.log(\"1 — sync\");\n\nsetTimeout(() => console.log(\"4 — setTimeout\"), 0);\n\nPromise.resolve().then(() => console.log(\"3 — microtask\"));\n\nconsole.log(\"2 — sync\");\n\n\u002F\u002F always: 1, 2, 3, 4",[],{"id":191,"html":192,"type":138},"b16","\u003Cp>That part you may already know. Here&#39;s the part that surprises people: it&#39;s not a head start, it&#39;s a \u003Cem>standing rule\u003C\u002Fem>. Chain ten \u003Ccode>.then()\u003C\u002Fcode> calls after that same \u003Ccode>setTimeout\u003C\u002Fcode>, each one scheduling the next only once the previous resolves, and all ten still finish before the timeout fires — because the microtask queue keeps draining as long as anything is in it, and a \u003Ccode>.then()\u003C\u002Fcode> callback adds its continuation back onto that same queue. The \u003Ccode>setTimeout\u003C\u002Fcode> doesn&#39;t get a turn until the queue is empty, however long that takes.\u003C\u002Fp>",{"id":194,"html":195,"type":138},"b17","\u003C!-- playground:start -->",{"id":197,"html":198,"text":198,"type":148,"level":31},"b18","🎮 Try it yourself",{"id":200,"html":201,"type":138},"b19","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fqueuemicrotask-microtask-vs-macrotask\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":203,"html":204,"type":138},"b20","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":206,"html":207,"type":138},"b21","\u003C!-- playground:end -->",{"id":209,"html":210,"text":211,"type":148,"level":31},"b22","The fix: schedule into the queue you&#39;re actually being checked against","The fix: schedule into the queue you're actually being checked against",{"id":213,"html":214,"type":138},"b23","\u003Cp>Swap the one line:\u003C\u002Fp>",{"id":216,"code":217,"type":155,"language":156,"highlight":218},"b24","function queueUpdate(key, value) {\n  pending.set(key, value);\n  if (!scheduled) {\n    scheduled = true;\n    queueMicrotask(flush); \u002F\u002F same queue as every .then() and every await\n  }\n}",[],{"id":220,"html":221,"type":138},"b25","\u003Cp>\u003Ccode>queueMicrotask\u003C\u002Fcode> still coalesces every synchronous call in the current burst into a single \u003Ccode>flush\u003C\u002Fcode> — that part of the behavior doesn&#39;t change, because the microtask checkpoint only runs once the call stack is empty either way. What changes is \u003Cem>which\u003C\u002Fem> queue \u003Ccode>flush\u003C\u002Fcode> sits in. Now it&#39;s the same queue \u003Ccode>await Promise.resolve()\u003C\u002Fcode> empties. The test passes, because \u003Ccode>flush\u003C\u002Fcode> — queued first — runs before the test&#39;s own \u003Ccode>await\u003C\u002Fcode> gets its turn, not after.\u003C\u002Fp>",{"id":223,"html":224,"type":138},"b26","\u003Cp>\u003Ccode>queueMicrotask\u003C\u002Fcode> isn&#39;t exotic: it&#39;s Baseline, widely supported since 2020 in every major browser, and it&#39;s been a global in Node since v11. It takes a callback, returns nothing, and that&#39;s the entire API.\u003C\u002Fp>",{"id":226,"html":227,"text":227,"type":148,"level":31},"b27","Where setTimeout is still the right call",{"id":229,"html":230,"type":138},"b28","\u003Cp>Don&#39;t read this as &quot;replace every \u003Ccode>setTimeout(0)\u003C\u002Fcode>.&quot; Microtasks are not free — the queue has to fully drain before the browser can render a frame or respond to a click, so a microtask that keeps re-queuing itself can freeze the page solid. If what you actually need is to let the browser breathe — paint a frame, handle an input event — between chunks of work, you want a macrotask-shaped yield (\u003Ccode>setTimeout\u003C\u002Fcode>, or \u003Ccode>scheduler.yield\u003C\u002Fcode> where it&#39;s available), not a microtask. \u003Ccode>queueMicrotask\u003C\u002Fcode> answers &quot;run this before anything else gets a turn.&quot; It is the wrong tool for &quot;give the user a moment.&quot;\u003C\u002Fp>",{"id":232,"html":233,"type":138},"b29","\u003Cp>One more gotcha worth knowing if you touch Node: \u003Ccode>process.nextTick()\u003C\u002Fcode> jumps the line even ahead of \u003Ccode>queueMicrotask\u003C\u002Fcode> — Node fully drains its \u003Ccode>nextTick\u003C\u002Fcode> queue before the promise\u002Fmicrotask queue on every turn. If you&#39;re writing isomorphic code, \u003Ccode>queueMicrotask\u003C\u002Fcode> is the one that behaves the same in the browser and in Node; \u003Ccode>process.nextTick\u003C\u002Fcode> doesn&#39;t exist in the browser at all.\u003C\u002Fp>",{"id":235,"html":236,"type":138},"b30","\u003Cp>The test wasn&#39;t wrong, and neither was the original instinct to defer the flush. \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode> was just answering a different question than the one being asked — &quot;run after the next paint,&quot; instead of &quot;run after absolutely everything currently promised, and not one tick later.&quot; \u003Ccode>queueMicrotask\u003C\u002Fcode> is the second sentence, spelled correctly.\u003C\u002Fp>",{"id":238,"html":239,"type":138},"b31","\u003C!-- quiz:start -->",{"id":241,"html":242,"text":242,"type":148,"level":31},"b32","🧠 Test yourself",{"id":244,"html":245,"type":138},"b33","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fqueuemicrotask-microtask-vs-macrotask\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":247,"html":248,"type":138},"b34","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":250,"html":251,"type":138},"b35","\u003C!-- quiz:end -->",{"id":253,"html":254,"type":138},"b36","\u003Cp>Where else in your code is a \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode> quietly promising &quot;right after&quot; but delivering &quot;eventually&quot;? I&#39;d bet there&#39;s at least one test file that already knows.\u003C\u002Fp>",{"id":256,"html":257,"type":138},"b37","\u003C!-- related:start -->",{"id":259,"html":260,"text":260,"type":148,"level":31},"b38","📚 Read next",{"id":262,"type":176,"items":263,"ordered":18},"b39",[264,265,266],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpromise-try-synchronous-throw-safety\">My .catch() Never Ran. The Bug Was Two Lines Above It\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ffetch-retry-exponential-backoff\">Stop Retrying Immediately. Exponential Backoff Fixes It.\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Frequestanimationframe-scroll-throttling\">You&#39;re Debouncing Scroll Wrong. requestAnimationFrame Fixes It.\u003C\u002Fa>",{"id":268,"html":269,"type":138},"b40","\u003C!-- related:end -->",{"id":271,"type":272},"b41","divider",{"id":274,"html":275,"type":138},"b42","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":277,"html":278,"type":138},"b43","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":280,"type":176,"items":281,"ordered":18},"b44",[282,283,284],"⭐ \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>","The test called the function, then did `await Promise.resolve()`, then checked the state. It failed. Not flaky — failed every single run, for this one function and no other.\n\nThe function wasn't broken. It deferred its work with `setTimeout(fn, 0)`, and `await Promise.resolve()` doesn't wait for that. It can't. By the time `setTimeout` even gets a turn, that `await` is long finished.\n\nThe fix was a one-word swap. Understanding why it mattered took longer.\n\n## The batcher that looked obviously right\n\nHere's the function in question — a tiny update batcher for a settings panel. Call it five times in a row and it should write once, not five times:\n\n```js\nlet pending = new Map();\nlet scheduled = false;\n\nfunction queueUpdate(key, value) {\n  pending.set(key, value);\n  if (!scheduled) {\n    scheduled = true;\n    setTimeout(flush, 0); \u002F\u002F \"run this once, right after the current burst of calls\"\n  }\n}\n\nfunction flush() {\n  scheduled = false;\n  applyUpdates(pending);\n  pending.clear();\n}\n```\n\nClick a few toggles fast, and `flush` runs once with the final values. Manual testing looks perfect. Then someone writes the obvious test:\n\n```js\ntest(\"coalesces rapid updates into one flush\", async () => {\n  queueUpdate(\"theme\", \"dark\");\n  queueUpdate(\"theme\", \"darker\");\n\n  await Promise.resolve(); \u002F\u002F \"let things settle\"\n\n  expect(getStoreValue(\"theme\")).toBe(\"darker\"); \u002F\u002F fails — still unset\n});\n```\n\nThat `await Promise.resolve()` is a common idiom for \"let the microtask queue drain before I assert.\" It isn't broken, and it isn't a flake you can retry away. `flush` genuinely has not run yet when the assertion checks — because `setTimeout`, even at `0`, schedules into a queue that a microtask-flushing `await` never visits.\n\n## Two queues, and one of them always finishes first\n\nEvery JavaScript engine actually runs two separate queues, and they are not interchangeable:\n\n- **The microtask queue** — where `Promise.resolve().then(fn)` and `queueMicrotask(fn)` both land, in the same queue.\n- **The macrotask (task) queue** — where `setTimeout`, UI events, and I\u002FO callbacks land.\n\nThe HTML spec's event loop rule is blunt about the order: once the current script finishes, the engine drains the *entire* microtask queue — including any new microtasks queued while draining — before it's allowed to touch the next macrotask, or render a frame. This isn't \"promises happen to be fast.\" It's a hard ordering guarantee, and every major engine (V8, SpiderMonkey, JavaScriptCore) implements it the same way.\n\nThe classic proof:\n\n```js\nconsole.log(\"1 — sync\");\n\nsetTimeout(() => console.log(\"4 — setTimeout\"), 0);\n\nPromise.resolve().then(() => console.log(\"3 — microtask\"));\n\nconsole.log(\"2 — sync\");\n\n\u002F\u002F always: 1, 2, 3, 4\n```\n\nThat part you may already know. Here's the part that surprises people: it's not a head start, it's a *standing rule*. Chain ten `.then()` calls after that same `setTimeout`, each one scheduling the next only once the previous resolves, and all ten still finish before the timeout fires — because the microtask queue keeps draining as long as anything is in it, and a `.then()` callback adds its continuation back onto that same queue. The `setTimeout` doesn't get a turn until the queue is empty, however long that takes.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fqueuemicrotask-microtask-vs-macrotask\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The fix: schedule into the queue you're actually being checked against\n\nSwap the one line:\n\n```js\nfunction queueUpdate(key, value) {\n  pending.set(key, value);\n  if (!scheduled) {\n    scheduled = true;\n    queueMicrotask(flush); \u002F\u002F same queue as every .then() and every await\n  }\n}\n```\n\n`queueMicrotask` still coalesces every synchronous call in the current burst into a single `flush` — that part of the behavior doesn't change, because the microtask checkpoint only runs once the call stack is empty either way. What changes is *which* queue `flush` sits in. Now it's the same queue `await Promise.resolve()` empties. The test passes, because `flush` — queued first — runs before the test's own `await` gets its turn, not after.\n\n`queueMicrotask` isn't exotic: it's Baseline, widely supported since 2020 in every major browser, and it's been a global in Node since v11. It takes a callback, returns nothing, and that's the entire API.\n\n## Where setTimeout is still the right call\n\nDon't read this as \"replace every `setTimeout(0)`.\" Microtasks are not free — the queue has to fully drain before the browser can render a frame or respond to a click, so a microtask that keeps re-queuing itself can freeze the page solid. If what you actually need is to let the browser breathe — paint a frame, handle an input event — between chunks of work, you want a macrotask-shaped yield (`setTimeout`, or `scheduler.yield` where it's available), not a microtask. `queueMicrotask` answers \"run this before anything else gets a turn.\" It is the wrong tool for \"give the user a moment.\"\n\nOne more gotcha worth knowing if you touch Node: `process.nextTick()` jumps the line even ahead of `queueMicrotask` — Node fully drains its `nextTick` queue before the promise\u002Fmicrotask queue on every turn. If you're writing isomorphic code, `queueMicrotask` is the one that behaves the same in the browser and in Node; `process.nextTick` doesn't exist in the browser at all.\n\nThe test wasn't wrong, and neither was the original instinct to defer the flush. `setTimeout(fn, 0)` was just answering a different question than the one being asked — \"run after the next paint,\" instead of \"run after absolutely everything currently promised, and not one tick later.\" `queueMicrotask` is the second sentence, spelled correctly.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fqueuemicrotask-microtask-vs-macrotask\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\nWhere else in your code is a `setTimeout(fn, 0)` quietly promising \"right after\" but delivering \"eventually\"? I'd bet there's at least one test file that already knows.\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [My .catch() Never Ran. The Bug Was Two Lines Above It](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpromise-try-synchronous-throw-safety)\n- [Stop Retrying Immediately. Exponential Backoff Fixes It.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ffetch-retry-exponential-backoff)\n- [You're Debouncing Scroll Wrong. requestAnimationFrame Fixes It.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Frequestanimationframe-scroll-throttling)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\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)",{"title":111,"canonical":287,"description":288},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fqueuemicrotask-microtask-vs-macrotask","setTimeout(fn, 0) feels like 'run this right after the current code.' It isn't — every pending promise cuts in line first. queueMicrotask puts your callback in that same fast lane,","01a1a52e-1213-71ac-8458-b921b0a52f8b",{"id":291,"locked":18},"01a1a52e-1246-7799-86bc-a1be70bcad9a",[293],{"id":45,"slug":46,"title":48,"_count":294},{"questions":51},[296],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":298,"questionCount":51},{"questions":51},[]]