[{"data":1,"prerenderedAt":953},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjavascript-event-loop-explained":3},{"id":4,"title":5,"body":6,"description":938,"difficulty":939,"extension":940,"framework":941,"frameworkSlug":942,"meta":943,"navigation":944,"order":73,"path":945,"qaPath":946,"seo":947,"stem":948,"subtopic":949,"topic":950,"topicSlug":502,"updated":951,"__hash__":952},"blog\u002Fblog\u002Fjavascript-event-loop-explained.md","The JavaScript Event Loop — How Asynchronous Code Really Works",{"type":7,"value":8,"toc":926},"minimark",[9,14,32,36,39,119,126,130,142,184,195,199,206,254,265,358,364,368,456,480,565,582,586,597,605,618,622,637,730,735,739,742,756,807,827,831,890,901,905,922],[10,11,13],"h2",{"id":12},"understanding-the-javascript-event-loop","Understanding the JavaScript event loop",[15,16,17,18,22,23,27,28,31],"p",{},"JavaScript is single-threaded — it runs one piece of code at a time on one call\nstack. Yet it handles timers, network requests, user clicks, and animations\nseemingly all at once. The mechanism that makes that possible is the ",[19,20,21],"strong",{},"event loop",".\nUnderstanding it is the key to predicting output order, avoiding frozen UIs, and\nreasoning about ",[24,25,26],"code",{},"setTimeout",", promises, and ",[24,29,30],{},"async\u002Fawait",". This guide builds the\nmodel piece by piece.",[10,33,35],{"id":34},"the-call-stack","The call stack",[15,37,38],{},"The call stack is a LIFO structure that tracks the function currently executing.\nCalling a function pushes a frame; returning pops it. There's exactly one stack, so\nJavaScript does one thing at a time.",[40,41,46],"pre",{"className":42,"code":43,"language":44,"meta":45,"style":45},"language-js shiki shiki-themes github-light github-dark","function a() { b() }\nfunction b() { c() }\nfunction c() { return 1 }\na() \u002F\u002F stack grows a -> b -> c, then unwinds\n","js","",[24,47,48,71,86,106],{"__ignoreMap":45},[49,50,53,57,61,65,68],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"szBVR","function",[49,58,60],{"class":59},"sScJk"," a",[49,62,64],{"class":63},"sVt8B","() { ",[49,66,67],{"class":59},"b",[49,69,70],{"class":63},"() }\n",[49,72,74,76,79,81,84],{"class":51,"line":73},2,[49,75,56],{"class":55},[49,77,78],{"class":59}," b",[49,80,64],{"class":63},[49,82,83],{"class":59},"c",[49,85,70],{"class":63},[49,87,89,91,94,96,99,103],{"class":51,"line":88},3,[49,90,56],{"class":55},[49,92,93],{"class":59}," c",[49,95,64],{"class":63},[49,97,98],{"class":55},"return",[49,100,102],{"class":101},"sj4cs"," 1",[49,104,105],{"class":63}," }\n",[49,107,109,112,115],{"class":51,"line":108},4,[49,110,111],{"class":59},"a",[49,113,114],{"class":63},"() ",[49,116,118],{"class":117},"sJ8bj","\u002F\u002F stack grows a -> b -> c, then unwinds\n",[15,120,121,122,125],{},"Because there's only one stack, any long synchronous function ",[19,123,124],{},"blocks"," everything\nuntil it returns — no timers fire, no clicks are handled, nothing paints.",[10,127,129],{"id":128},"why-single-threaded-and-what-async-really-means","Why single-threaded, and what \"async\" really means",[15,131,132,133,137,138,141],{},"JavaScript was designed single-threaded to keep the DOM simple: if multiple threads\ncould mutate the DOM at once, you'd need locks everywhere. Asynchronicity doesn't add\nthreads — the ",[134,135,136],"em",{},"waiting"," (timers, I\u002FO, network) happens ",[19,139,140],{},"outside"," the JS engine, in\nthe browser or Node runtime. When that work finishes, a callback is queued to run on\nthe same single thread once the stack is clear.",[40,143,145],{"className":42,"code":144,"language":44,"meta":45,"style":45},"setTimeout(() => console.log('later'), 1000)\n\u002F\u002F the timer is handled by the browser; the callback runs on the JS thread later\n",[24,146,147,179],{"__ignoreMap":45},[49,148,149,151,154,157,160,163,166,170,173,176],{"class":51,"line":52},[49,150,26],{"class":59},[49,152,153],{"class":63},"(() ",[49,155,156],{"class":55},"=>",[49,158,159],{"class":63}," console.",[49,161,162],{"class":59},"log",[49,164,165],{"class":63},"(",[49,167,169],{"class":168},"sZZnC","'later'",[49,171,172],{"class":63},"), ",[49,174,175],{"class":101},"1000",[49,177,178],{"class":63},")\n",[49,180,181],{"class":51,"line":73},[49,182,183],{"class":117},"\u002F\u002F the timer is handled by the browser; the callback runs on the JS thread later\n",[15,185,186,187,190,191,194],{},"True parallelism requires ",[19,188,189],{},"Web Workers"," (browser) or ",[19,192,193],{},"worker threads"," (Node),\nwhich run separate threads that communicate via messages.",[10,196,198],{"id":197},"the-loop-tasks-and-microtasks","The loop: tasks and microtasks",[15,200,201,202,205],{},"When an async operation completes, its callback goes into a ",[19,203,204],{},"queue",". There are two\npriority levels:",[207,208,209,227],"ul",{},[210,211,212,215,216,218,219,222,223,226],"li",{},[19,213,214],{},"Macrotasks"," (tasks): ",[24,217,26],{},", ",[24,220,221],{},"setInterval",", I\u002FO, UI events. The loop runs\n",[19,224,225],{},"one"," per iteration.",[210,228,229,232,233,236,237,236,240,218,243,246,247,218,250,253],{},[19,230,231],{},"Microtasks",": promise ",[24,234,235],{},".then","\u002F",[24,238,239],{},"catch",[24,241,242],{},"finally",[24,244,245],{},"await"," continuations,\n",[24,248,249],{},"queueMicrotask",[24,251,252],{},"MutationObserver",".",[15,255,256,257,260,261,264],{},"The defining rule: after each macrotask (and after the initial synchronous script),\nthe engine ",[19,258,259],{},"drains the entire microtask queue"," — including microtasks scheduled by\nother microtasks — ",[134,262,263],{},"before"," the next macrotask or any rendering.",[40,266,268],{"className":42,"code":267,"language":44,"meta":45,"style":45},"setTimeout(() => console.log('macro'), 0)\nPromise.resolve()\n  .then(() => console.log('micro 1'))\n  .then(() => console.log('micro 2'))\n\u002F\u002F micro 1, micro 2, macro\n",[24,269,270,294,307,331,352],{"__ignoreMap":45},[49,271,272,274,276,278,280,282,284,287,289,292],{"class":51,"line":52},[49,273,26],{"class":59},[49,275,153],{"class":63},[49,277,156],{"class":55},[49,279,159],{"class":63},[49,281,162],{"class":59},[49,283,165],{"class":63},[49,285,286],{"class":168},"'macro'",[49,288,172],{"class":63},[49,290,291],{"class":101},"0",[49,293,178],{"class":63},[49,295,296,299,301,304],{"class":51,"line":73},[49,297,298],{"class":101},"Promise",[49,300,253],{"class":63},[49,302,303],{"class":59},"resolve",[49,305,306],{"class":63},"()\n",[49,308,309,312,315,317,319,321,323,325,328],{"class":51,"line":88},[49,310,311],{"class":63},"  .",[49,313,314],{"class":59},"then",[49,316,153],{"class":63},[49,318,156],{"class":55},[49,320,159],{"class":63},[49,322,162],{"class":59},[49,324,165],{"class":63},[49,326,327],{"class":168},"'micro 1'",[49,329,330],{"class":63},"))\n",[49,332,333,335,337,339,341,343,345,347,350],{"class":51,"line":108},[49,334,311],{"class":63},[49,336,314],{"class":59},[49,338,153],{"class":63},[49,340,156],{"class":55},[49,342,159],{"class":63},[49,344,162],{"class":59},[49,346,165],{"class":63},[49,348,349],{"class":168},"'micro 2'",[49,351,330],{"class":63},[49,353,355],{"class":51,"line":354},5,[49,356,357],{"class":117},"\u002F\u002F micro 1, micro 2, macro\n",[15,359,360,361,253],{},"Both microtasks finish before the timer, even with a 0 ms delay — which is why a\nresolved promise always beats ",[24,362,363],{},"setTimeout(0)",[10,365,367],{"id":366},"a-worked-example","A worked example",[40,369,371],{"className":42,"code":370,"language":44,"meta":45,"style":45},"console.log('A')\nsetTimeout(() => console.log('B'), 0)\nPromise.resolve().then(() => console.log('C'))\nconsole.log('D')\n\u002F\u002F Output: A D C B\n",[24,372,373,387,410,438,451],{"__ignoreMap":45},[49,374,375,378,380,382,385],{"class":51,"line":52},[49,376,377],{"class":63},"console.",[49,379,162],{"class":59},[49,381,165],{"class":63},[49,383,384],{"class":168},"'A'",[49,386,178],{"class":63},[49,388,389,391,393,395,397,399,401,404,406,408],{"class":51,"line":73},[49,390,26],{"class":59},[49,392,153],{"class":63},[49,394,156],{"class":55},[49,396,159],{"class":63},[49,398,162],{"class":59},[49,400,165],{"class":63},[49,402,403],{"class":168},"'B'",[49,405,172],{"class":63},[49,407,291],{"class":101},[49,409,178],{"class":63},[49,411,412,414,416,418,421,423,425,427,429,431,433,436],{"class":51,"line":88},[49,413,298],{"class":101},[49,415,253],{"class":63},[49,417,303],{"class":59},[49,419,420],{"class":63},"().",[49,422,314],{"class":59},[49,424,153],{"class":63},[49,426,156],{"class":55},[49,428,159],{"class":63},[49,430,162],{"class":59},[49,432,165],{"class":63},[49,434,435],{"class":168},"'C'",[49,437,330],{"class":63},[49,439,440,442,444,446,449],{"class":51,"line":108},[49,441,377],{"class":63},[49,443,162],{"class":59},[49,445,165],{"class":63},[49,447,448],{"class":168},"'D'",[49,450,178],{"class":63},[49,452,453],{"class":51,"line":354},[49,454,455],{"class":117},"\u002F\u002F Output: A D C B\n",[15,457,458,459,218,462,465,466,469,470,473,474,476,477,479],{},"Walk it through: synchronous logs first (",[24,460,461],{},"A",[24,463,464],{},"D","); the timer callback is queued as a\nmacrotask; the promise callback as a microtask. The script ends, microtasks drain\n(",[24,467,468],{},"C","), then the next macrotask runs (",[24,471,472],{},"B","). Add ",[24,475,30],{}," and the rule still holds —\ncode after an ",[24,478,245],{}," becomes a microtask continuation:",[40,481,483],{"className":42,"code":482,"language":44,"meta":45,"style":45},"console.log('1')\nasync function f() { console.log('2'); await null; console.log('3') }\nf()\nconsole.log('4')\n\u002F\u002F 1, 2, 4, 3\n",[24,484,485,498,540,547,560],{"__ignoreMap":45},[49,486,487,489,491,493,496],{"class":51,"line":52},[49,488,377],{"class":63},[49,490,162],{"class":59},[49,492,165],{"class":63},[49,494,495],{"class":168},"'1'",[49,497,178],{"class":63},[49,499,500,503,506,509,512,514,516,519,522,524,527,530,532,534,537],{"class":51,"line":73},[49,501,502],{"class":55},"async",[49,504,505],{"class":55}," function",[49,507,508],{"class":59}," f",[49,510,511],{"class":63},"() { console.",[49,513,162],{"class":59},[49,515,165],{"class":63},[49,517,518],{"class":168},"'2'",[49,520,521],{"class":63},"); ",[49,523,245],{"class":55},[49,525,526],{"class":101}," null",[49,528,529],{"class":63},"; console.",[49,531,162],{"class":59},[49,533,165],{"class":63},[49,535,536],{"class":168},"'3'",[49,538,539],{"class":63},") }\n",[49,541,542,545],{"class":51,"line":88},[49,543,544],{"class":59},"f",[49,546,306],{"class":63},[49,548,549,551,553,555,558],{"class":51,"line":108},[49,550,377],{"class":63},[49,552,162],{"class":59},[49,554,165],{"class":63},[49,556,557],{"class":168},"'4'",[49,559,178],{"class":63},[49,561,562],{"class":51,"line":354},[49,563,564],{"class":117},"\u002F\u002F 1, 2, 4, 3\n",[15,566,567,568,570,571,574,575,578,579,253],{},"The body before the first ",[24,569,245],{}," runs synchronously (",[24,572,573],{},"2","); everything after is\ndeferred as a microtask (",[24,576,577],{},"3","), which runs after the synchronous ",[24,580,581],{},"4",[10,583,585],{"id":584},"where-rendering-and-requestanimationframe-fit","Where rendering and requestAnimationFrame fit",[15,587,588,589,592,593,596],{},"The browser tries to paint at most once per frame (~16.7 ms at 60 fps), and it does so\n",[19,590,591],{},"after"," the current macrotask ",[134,594,595],{},"and"," its full microtask queue have completed — never\nmid-task.",[40,598,603],{"className":599,"code":601,"language":602},[600],"language-text","[1 macrotask] -> [drain all microtasks] -> [rAF callbacks] -> [style\u002Flayout\u002Fpaint] -> repeat\n","text",[24,604,601],{"__ignoreMap":45},[15,606,607,610,611,614,615,617],{},[24,608,609],{},"requestAnimationFrame(cb)"," schedules ",[24,612,613],{},"cb"," right before the next repaint, synced to the\ndisplay refresh and paused in background tabs — which is why it's the right tool for\nanimation, not ",[24,616,26],{},". Processing one macrotask per iteration (rather than the\nwhole queue) keeps input handling and painting responsive.",[10,619,621],{"id":620},"nodejs-specifics","Node.js specifics",[15,623,624,625,628,629,632,633,636],{},"Node uses libuv and runs the loop through ordered ",[19,626,627],{},"phases"," — timers, pending\ncallbacks, poll, check, close — draining ",[24,630,631],{},"process.nextTick"," and microtasks ",[19,634,635],{},"between\nevery phase",". Two Node-only schedulers matter:",[40,638,640],{"className":42,"code":639,"language":44,"meta":45,"style":45},"setImmediate(() => console.log('immediate')) \u002F\u002F \"check\" phase\nsetTimeout(() => console.log('timeout'), 0)   \u002F\u002F \"timers\" phase\nprocess.nextTick(() => console.log('nextTick'))\u002F\u002F before everything\n\u002F\u002F nextTick first; immediate vs timeout order varies at the top level,\n\u002F\u002F but inside an I\u002FO callback setImmediate is deterministically first\n",[24,641,642,666,693,720,725],{"__ignoreMap":45},[49,643,644,647,649,651,653,655,657,660,663],{"class":51,"line":52},[49,645,646],{"class":59},"setImmediate",[49,648,153],{"class":63},[49,650,156],{"class":55},[49,652,159],{"class":63},[49,654,162],{"class":59},[49,656,165],{"class":63},[49,658,659],{"class":168},"'immediate'",[49,661,662],{"class":63},")) ",[49,664,665],{"class":117},"\u002F\u002F \"check\" phase\n",[49,667,668,670,672,674,676,678,680,683,685,687,690],{"class":51,"line":73},[49,669,26],{"class":59},[49,671,153],{"class":63},[49,673,156],{"class":55},[49,675,159],{"class":63},[49,677,162],{"class":59},[49,679,165],{"class":63},[49,681,682],{"class":168},"'timeout'",[49,684,172],{"class":63},[49,686,291],{"class":101},[49,688,689],{"class":63},")   ",[49,691,692],{"class":117},"\u002F\u002F \"timers\" phase\n",[49,694,695,698,701,703,705,707,709,711,714,717],{"class":51,"line":88},[49,696,697],{"class":63},"process.",[49,699,700],{"class":59},"nextTick",[49,702,153],{"class":63},[49,704,156],{"class":55},[49,706,159],{"class":63},[49,708,162],{"class":59},[49,710,165],{"class":63},[49,712,713],{"class":168},"'nextTick'",[49,715,716],{"class":63},"))",[49,718,719],{"class":117},"\u002F\u002F before everything\n",[49,721,722],{"class":51,"line":108},[49,723,724],{"class":117},"\u002F\u002F nextTick first; immediate vs timeout order varies at the top level,\n",[49,726,727],{"class":51,"line":354},[49,728,729],{"class":117},"\u002F\u002F but inside an I\u002FO callback setImmediate is deterministically first\n",[15,731,732,734],{},[24,733,631],{}," has even higher priority than promises, so recursively scheduling\nit can starve the loop entirely.",[10,736,738],{"id":737},"blocking-and-starvation","Blocking and starvation",[15,740,741],{},"Two ways to wedge the loop:",[207,743,744,750],{},[210,745,746,749],{},[19,747,748],{},"A long synchronous task"," never lets the stack empty, so timers, promises, input,\nand rendering all stall (the \"page unresponsive\" freeze, and poor INP).",[210,751,752,755],{},[19,753,754],{},"Microtask starvation"," — microtasks that keep scheduling more microtasks drain\nforever, so macrotasks and rendering never get a turn.",[40,757,759],{"className":42,"code":758,"language":44,"meta":45,"style":45},"const end = Date.now() + 3000\nwhile (Date.now() \u003C end) {} \u002F\u002F freezes the tab for 3 seconds\n",[24,760,761,786],{"__ignoreMap":45},[49,762,763,766,769,772,775,778,780,783],{"class":51,"line":52},[49,764,765],{"class":55},"const",[49,767,768],{"class":101}," end",[49,770,771],{"class":55}," =",[49,773,774],{"class":63}," Date.",[49,776,777],{"class":59},"now",[49,779,114],{"class":63},[49,781,782],{"class":55},"+",[49,784,785],{"class":101}," 3000\n",[49,787,788,791,794,796,798,801,804],{"class":51,"line":73},[49,789,790],{"class":55},"while",[49,792,793],{"class":63}," (Date.",[49,795,777],{"class":59},[49,797,114],{"class":63},[49,799,800],{"class":55},"\u003C",[49,802,803],{"class":63}," end) {} ",[49,805,806],{"class":117},"\u002F\u002F freezes the tab for 3 seconds\n",[15,808,809,810,813,814,218,816,218,819,822,823,826],{},"The fix is to ",[19,811,812],{},"yield",": break long work into chunks scheduled as macrotasks\n(",[24,815,26],{},[24,817,818],{},"MessageChannel",[24,820,821],{},"scheduler.yield()","), or move CPU-heavy work into a\n",[19,824,825],{},"Web Worker"," so the main thread stays free for input and paint.",[10,828,830],{"id":829},"choosing-how-to-defer-work","Choosing how to defer work",[40,832,834],{"className":42,"code":833,"language":44,"meta":45,"style":45},"queueMicrotask(fn)         \u002F\u002F ASAP, before the next render\u002Fmacrotask\nPromise.resolve().then(fn) \u002F\u002F also a microtask\nsetTimeout(fn, 0)          \u002F\u002F next macrotask (after microtasks, maybe a paint)\nrequestAnimationFrame(fn)  \u002F\u002F right before the next paint\n",[24,835,836,846,864,879],{"__ignoreMap":45},[49,837,838,840,843],{"class":51,"line":52},[49,839,249],{"class":59},[49,841,842],{"class":63},"(fn)         ",[49,844,845],{"class":117},"\u002F\u002F ASAP, before the next render\u002Fmacrotask\n",[49,847,848,850,852,854,856,858,861],{"class":51,"line":73},[49,849,298],{"class":101},[49,851,253],{"class":63},[49,853,303],{"class":59},[49,855,420],{"class":63},[49,857,314],{"class":59},[49,859,860],{"class":63},"(fn) ",[49,862,863],{"class":117},"\u002F\u002F also a microtask\n",[49,865,866,868,871,873,876],{"class":51,"line":88},[49,867,26],{"class":59},[49,869,870],{"class":63},"(fn, ",[49,872,291],{"class":101},[49,874,875],{"class":63},")          ",[49,877,878],{"class":117},"\u002F\u002F next macrotask (after microtasks, maybe a paint)\n",[49,880,881,884,887],{"class":51,"line":108},[49,882,883],{"class":59},"requestAnimationFrame",[49,885,886],{"class":63},"(fn)  ",[49,888,889],{"class":117},"\u002F\u002F right before the next paint\n",[15,891,892,893,896,897,900],{},"Pick a ",[19,894,895],{},"microtask"," when the deferral must beat rendering and timers; pick a\n",[19,898,899],{},"macrotask"," when you want to yield the thread so input and paint can happen.",[10,902,904],{"id":903},"recap","Recap",[15,906,907,908,911,912,915,916,918,919,921],{},"JavaScript runs on one thread with one call stack; the event loop coordinates\nconcurrency by running queued callbacks when the stack is empty. Synchronous code runs\nfirst, then the ",[19,909,910],{},"entire microtask queue"," drains, then ",[19,913,914],{},"one macrotask",", with\nrendering interleaved — repeat. Promises and ",[24,917,245],{}," are microtasks; ",[24,920,26],{}," and\nI\u002FO are macrotasks, so microtasks always win. Keep tasks short and yield for long work,\nand the puzzles, the freezes, and the scheduling questions all become predictable.",[923,924,925],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":45,"searchDepth":73,"depth":73,"links":927},[928,929,930,931,932,933,934,935,936,937],{"id":12,"depth":73,"text":13},{"id":34,"depth":73,"text":35},{"id":128,"depth":73,"text":129},{"id":197,"depth":73,"text":198},{"id":366,"depth":73,"text":367},{"id":584,"depth":73,"text":585},{"id":620,"depth":73,"text":621},{"id":737,"depth":73,"text":738},{"id":829,"depth":73,"text":830},{"id":903,"depth":73,"text":904},"JavaScript event loop interview questions — the call stack, task and microtask queues, and how asynchronous code is scheduled.","hard","md","JavaScript","javascript",{},true,"\u002Fblog\u002Fjavascript-event-loop-explained","\u002Fjavascript\u002Fasync\u002Fevent-loop",{"title":5,"description":938},"blog\u002Fjavascript-event-loop-explained","The Event Loop","Asynchronous JavaScript","2026-06-17","SX0FaJFWVQaaVesdxdiYIdY40XTXNZntrLYGIgUDBRQ",1781808673080]