[{"data":1,"prerenderedAt":2982},["ShallowReactive",2],{"page-\u002Fjs\u002F22-timers-and-scheduling":3},{"id":4,"title":5,"body":6,"description":2975,"extension":2976,"meta":2977,"navigation":90,"path":2978,"seo":2979,"stem":2980,"__hash__":2981},"content\u002Fjs\u002F22-timers-and-scheduling.md","JavaScript 22 — Timer Internals: setTimeout Drift, requestAnimationFrame & Scheduling Patterns",{"type":7,"value":8,"toc":2964},"minimark",[9,14,27,536,543,944,948,1663,1667,1965,1969,2417,2421,2721,2725,2732,2811,2960],[10,11,13],"h1",{"id":12},"_22-timer-internals-settimeout-drift-requestanimationframe-scheduling-patterns","22 — Timer Internals: setTimeout Drift, requestAnimationFrame & Scheduling Patterns",[15,16,18,22,23,26],"h2",{"id":17},"settimeout-setinterval-accuracy-and-drift",[19,20,21],"code",{},"setTimeout"," \u002F ",[19,24,25],{},"setInterval",": Accuracy and Drift",[28,29,31],"code-wrapper",{"language":30},"javascript",[32,33,37],"pre",{"className":34,"code":35,"language":30,"meta":36,"style":36},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F ── setTimeout: schedule a macrotask after a delay (minimum ~4ms in browsers) ──\nsetTimeout(() => console.log(\"runs after ~1000ms\"), 1000);\n\n\u002F\u002F ── ⚠️ setTimeout is NOT precise — it's a minimum, not exact ──\n\u002F\u002F The timer fires AFTER the delay, when the event loop reaches the macrotask queue.\n\u002F\u002F If the main thread is busy, the timer fires late.\n\n\u002F\u002F ── setTimeout(fn, 0) doesn't run immediately ──\nsetTimeout(() => console.log(\"C\"), 0);\nconsole.log(\"A\");\nPromise.resolve().then(() => console.log(\"B\"));\nconsole.log(\"D\");\n\u002F\u002F Output: A, D, B, C\n\u002F\u002F C runs last — setTimeout is a macrotask (after all microtasks)\n\n\u002F\u002F ── setInterval drift: intervals accumulate error ──\nlet expected = Date.now();\nsetInterval(() => {\n    const drift = Date.now() - expected;\n    console.log(\"drift:\", drift, \"ms\");\n    expected += 1000;  \u002F\u002F adjust for the expected time (not the actual)\n    \u002F\u002F If the callback takes time, the interval drifts (each tick is late by the previous work)\n}, 1000);\n\u002F\u002F setInterval fires at 1s intervals, but if each callback takes 200ms:\n\u002F\u002F Tick 1: fires at 1000ms, callback takes 200ms → ends at 1200ms\n\u002F\u002F Tick 2: fires at 2000ms (not 1200+1000=2200 — setInterval targets the original schedule)\n\u002F\u002F BUT: if the main thread is blocked, the next tick is delayed.\n\n\u002F\u002F ── Self-correcting timer (no drift) ──\nfunction accurateInterval(fn, delay) {\n    let expected = Date.now() + delay;\n    function tick() {\n        const drift = Date.now() - expected;  \u002F\u002F how late are we?\n        fn(drift);  \u002F\u002F pass drift to the callback\n        expected += delay;  \u002F\u002F next expected time\n        setTimeout(tick, Math.max(0, delay - drift));  \u002F\u002F correct for drift\n    }\n    setTimeout(tick, delay);\n}\n","",[19,38,39,48,85,92,98,104,110,115,121,146,161,195,209,215,221,226,232,253,265,290,311,329,335,345,351,357,363,369,374,380,404,426,438,462,474,488,515,521,530],{"__ignoreMap":36},[40,41,44],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sdCPZ","\u002F\u002F ── setTimeout: schedule a macrotask after a delay (minimum ~4ms in browsers) ──\n",[40,49,51,54,58,62,65,68,71,75,78,82],{"class":42,"line":50},2,[40,52,21],{"class":53},"sIsaT",[40,55,57],{"class":56},"ssxIu","(() ",[40,59,61],{"class":60},"svdQ7","=>",[40,63,64],{"class":56}," console.",[40,66,67],{"class":53},"log",[40,69,70],{"class":56},"(",[40,72,74],{"class":73},"sJ6F3","\"runs after ~1000ms\"",[40,76,77],{"class":56},"), ",[40,79,81],{"class":80},"snvgF","1000",[40,83,84],{"class":56},");\n",[40,86,88],{"class":42,"line":87},3,[40,89,91],{"emptyLinePlaceholder":90},true,"\n",[40,93,95],{"class":42,"line":94},4,[40,96,97],{"class":46},"\u002F\u002F ── ⚠️ setTimeout is NOT precise — it's a minimum, not exact ──\n",[40,99,101],{"class":42,"line":100},5,[40,102,103],{"class":46},"\u002F\u002F The timer fires AFTER the delay, when the event loop reaches the macrotask queue.\n",[40,105,107],{"class":42,"line":106},6,[40,108,109],{"class":46},"\u002F\u002F If the main thread is busy, the timer fires late.\n",[40,111,113],{"class":42,"line":112},7,[40,114,91],{"emptyLinePlaceholder":90},[40,116,118],{"class":42,"line":117},8,[40,119,120],{"class":46},"\u002F\u002F ── setTimeout(fn, 0) doesn't run immediately ──\n",[40,122,124,126,128,130,132,134,136,139,141,144],{"class":42,"line":123},9,[40,125,21],{"class":53},[40,127,57],{"class":56},[40,129,61],{"class":60},[40,131,64],{"class":56},[40,133,67],{"class":53},[40,135,70],{"class":56},[40,137,138],{"class":73},"\"C\"",[40,140,77],{"class":56},[40,142,143],{"class":80},"0",[40,145,84],{"class":56},[40,147,149,152,154,156,159],{"class":42,"line":148},10,[40,150,151],{"class":56},"console.",[40,153,67],{"class":53},[40,155,70],{"class":56},[40,157,158],{"class":73},"\"A\"",[40,160,84],{"class":56},[40,162,164,167,170,173,176,179,181,183,185,187,189,192],{"class":42,"line":163},11,[40,165,166],{"class":80},"Promise",[40,168,169],{"class":56},".",[40,171,172],{"class":53},"resolve",[40,174,175],{"class":56},"().",[40,177,178],{"class":53},"then",[40,180,57],{"class":56},[40,182,61],{"class":60},[40,184,64],{"class":56},[40,186,67],{"class":53},[40,188,70],{"class":56},[40,190,191],{"class":73},"\"B\"",[40,193,194],{"class":56},"));\n",[40,196,198,200,202,204,207],{"class":42,"line":197},12,[40,199,151],{"class":56},[40,201,67],{"class":53},[40,203,70],{"class":56},[40,205,206],{"class":73},"\"D\"",[40,208,84],{"class":56},[40,210,212],{"class":42,"line":211},13,[40,213,214],{"class":46},"\u002F\u002F Output: A, D, B, C\n",[40,216,218],{"class":42,"line":217},14,[40,219,220],{"class":46},"\u002F\u002F C runs last — setTimeout is a macrotask (after all microtasks)\n",[40,222,224],{"class":42,"line":223},15,[40,225,91],{"emptyLinePlaceholder":90},[40,227,229],{"class":42,"line":228},16,[40,230,231],{"class":46},"\u002F\u002F ── setInterval drift: intervals accumulate error ──\n",[40,233,235,238,241,244,247,250],{"class":42,"line":234},17,[40,236,237],{"class":60},"let",[40,239,240],{"class":56}," expected ",[40,242,243],{"class":60},"=",[40,245,246],{"class":56}," Date.",[40,248,249],{"class":53},"now",[40,251,252],{"class":56},"();\n",[40,254,256,258,260,262],{"class":42,"line":255},18,[40,257,25],{"class":53},[40,259,57],{"class":56},[40,261,61],{"class":60},[40,263,264],{"class":56}," {\n",[40,266,268,271,274,277,279,281,284,287],{"class":42,"line":267},19,[40,269,270],{"class":60},"    const",[40,272,273],{"class":80}," drift",[40,275,276],{"class":60}," =",[40,278,246],{"class":56},[40,280,249],{"class":53},[40,282,283],{"class":56},"() ",[40,285,286],{"class":60},"-",[40,288,289],{"class":56}," expected;\n",[40,291,293,296,298,300,303,306,309],{"class":42,"line":292},20,[40,294,295],{"class":56},"    console.",[40,297,67],{"class":53},[40,299,70],{"class":56},[40,301,302],{"class":73},"\"drift:\"",[40,304,305],{"class":56},", drift, ",[40,307,308],{"class":73},"\"ms\"",[40,310,84],{"class":56},[40,312,314,317,320,323,326],{"class":42,"line":313},21,[40,315,316],{"class":56},"    expected ",[40,318,319],{"class":60},"+=",[40,321,322],{"class":80}," 1000",[40,324,325],{"class":56},";  ",[40,327,328],{"class":46},"\u002F\u002F adjust for the expected time (not the actual)\n",[40,330,332],{"class":42,"line":331},22,[40,333,334],{"class":46},"    \u002F\u002F If the callback takes time, the interval drifts (each tick is late by the previous work)\n",[40,336,338,341,343],{"class":42,"line":337},23,[40,339,340],{"class":56},"}, ",[40,342,81],{"class":80},[40,344,84],{"class":56},[40,346,348],{"class":42,"line":347},24,[40,349,350],{"class":46},"\u002F\u002F setInterval fires at 1s intervals, but if each callback takes 200ms:\n",[40,352,354],{"class":42,"line":353},25,[40,355,356],{"class":46},"\u002F\u002F Tick 1: fires at 1000ms, callback takes 200ms → ends at 1200ms\n",[40,358,360],{"class":42,"line":359},26,[40,361,362],{"class":46},"\u002F\u002F Tick 2: fires at 2000ms (not 1200+1000=2200 — setInterval targets the original schedule)\n",[40,364,366],{"class":42,"line":365},27,[40,367,368],{"class":46},"\u002F\u002F BUT: if the main thread is blocked, the next tick is delayed.\n",[40,370,372],{"class":42,"line":371},28,[40,373,91],{"emptyLinePlaceholder":90},[40,375,377],{"class":42,"line":376},29,[40,378,379],{"class":46},"\u002F\u002F ── Self-correcting timer (no drift) ──\n",[40,381,383,386,389,391,395,398,401],{"class":42,"line":382},30,[40,384,385],{"class":60},"function",[40,387,388],{"class":53}," accurateInterval",[40,390,70],{"class":56},[40,392,394],{"class":393},"sCrzJ","fn",[40,396,397],{"class":56},", ",[40,399,400],{"class":393},"delay",[40,402,403],{"class":56},") {\n",[40,405,407,410,412,414,416,418,420,423],{"class":42,"line":406},31,[40,408,409],{"class":60},"    let",[40,411,240],{"class":56},[40,413,243],{"class":60},[40,415,246],{"class":56},[40,417,249],{"class":53},[40,419,283],{"class":56},[40,421,422],{"class":60},"+",[40,424,425],{"class":56}," delay;\n",[40,427,429,432,435],{"class":42,"line":428},32,[40,430,431],{"class":60},"    function",[40,433,434],{"class":53}," tick",[40,436,437],{"class":56},"() {\n",[40,439,441,444,446,448,450,452,454,456,459],{"class":42,"line":440},33,[40,442,443],{"class":60},"        const",[40,445,273],{"class":80},[40,447,276],{"class":60},[40,449,246],{"class":56},[40,451,249],{"class":53},[40,453,283],{"class":56},[40,455,286],{"class":60},[40,457,458],{"class":56}," expected;  ",[40,460,461],{"class":46},"\u002F\u002F how late are we?\n",[40,463,465,468,471],{"class":42,"line":464},34,[40,466,467],{"class":53},"        fn",[40,469,470],{"class":56},"(drift);  ",[40,472,473],{"class":46},"\u002F\u002F pass drift to the callback\n",[40,475,477,480,482,485],{"class":42,"line":476},35,[40,478,479],{"class":56},"        expected ",[40,481,319],{"class":60},[40,483,484],{"class":56}," delay;  ",[40,486,487],{"class":46},"\u002F\u002F next expected time\n",[40,489,491,494,497,500,502,504,507,509,512],{"class":42,"line":490},36,[40,492,493],{"class":53},"        setTimeout",[40,495,496],{"class":56},"(tick, Math.",[40,498,499],{"class":53},"max",[40,501,70],{"class":56},[40,503,143],{"class":80},[40,505,506],{"class":56},", delay ",[40,508,286],{"class":60},[40,510,511],{"class":56}," drift));  ",[40,513,514],{"class":46},"\u002F\u002F correct for drift\n",[40,516,518],{"class":42,"line":517},37,[40,519,520],{"class":56},"    }\n",[40,522,524,527],{"class":42,"line":523},38,[40,525,526],{"class":53},"    setTimeout",[40,528,529],{"class":56},"(tick, delay);\n",[40,531,533],{"class":42,"line":532},39,[40,534,535],{"class":56},"}\n",[15,537,539,542],{"id":538},"requestanimationframe-sync-with-display-refresh",[19,540,541],{},"requestAnimationFrame",": Sync with Display Refresh",[28,544,545],{"language":30},[32,546,548],{"className":34,"code":547,"language":30,"meta":36,"style":36},"\u002F\u002F ── requestAnimationFrame: runs before the browser paints (synced to refresh rate) ──\n\u002F\u002F ~60fps on most displays (16.67ms per frame), 120fps on some (8.33ms per frame).\n\u002F\u002F Use for animations — smooth and efficient (pauses when tab is hidden).\n\nlet start = performance.now();\nfunction animate(now) {\n    const elapsed = now - start;\n    const progress = Math.min(elapsed \u002F 1000, 1);  \u002F\u002F 0 → 1 over 1 second\n    element.style.opacity = progress;  \u002F\u002F fade in\n    if (progress \u003C 1) {\n        requestAnimationFrame(animate);  \u002F\u002F schedule next frame\n    }\n}\nrequestAnimationFrame(animate);  \u002F\u002F start the animation\n\n\u002F\u002F ── ❌ Don't use setInterval for animations ──\nsetInterval(() => {\n    element.style.left = parseInt(element.style.left || 0) + 1 + \"px\";\n}, 16);  \u002F\u002F ~60fps, but NOT synced with the display → jank, frame drops\n\u002F\u002F setInterval runs on the event loop (macrotask) — not aligned with paint.\n\u002F\u002F rAF runs just before paint — the browser can optimize rendering.\n\n\u002F\u002F ── Cancel animation ──\nlet animationId = requestAnimationFrame(animate);\ncancelAnimationFrame(animationId);  \u002F\u002F stop the animation\n\n\u002F\u002F ── requestIdleCallback: run work during idle time (low priority) ──\nrequestIdleCallback((deadline) => {\n    \u002F\u002F deadline.timeRemaining() → ms left in the current idle period\n    while (deadline.timeRemaining() > 0 && tasks.length > 0) {\n        processTask(tasks.shift());\n    }\n    if (tasks.length > 0) {\n        requestIdleCallback(processTasks);  \u002F\u002F continue in next idle period\n    }\n}, { timeout: 2000 });  \u002F\u002F must run within 2s (even if never idle)\n",[19,549,550,555,560,565,569,585,598,615,649,662,678,689,693,697,706,710,715,725,760,772,777,782,786,791,806,817,821,826,843,848,882,896,900,915,926,930],{"__ignoreMap":36},[40,551,552],{"class":42,"line":43},[40,553,554],{"class":46},"\u002F\u002F ── requestAnimationFrame: runs before the browser paints (synced to refresh rate) ──\n",[40,556,557],{"class":42,"line":50},[40,558,559],{"class":46},"\u002F\u002F ~60fps on most displays (16.67ms per frame), 120fps on some (8.33ms per frame).\n",[40,561,562],{"class":42,"line":87},[40,563,564],{"class":46},"\u002F\u002F Use for animations — smooth and efficient (pauses when tab is hidden).\n",[40,566,567],{"class":42,"line":94},[40,568,91],{"emptyLinePlaceholder":90},[40,570,571,573,576,578,581,583],{"class":42,"line":100},[40,572,237],{"class":60},[40,574,575],{"class":56}," start ",[40,577,243],{"class":60},[40,579,580],{"class":56}," performance.",[40,582,249],{"class":53},[40,584,252],{"class":56},[40,586,587,589,592,594,596],{"class":42,"line":106},[40,588,385],{"class":60},[40,590,591],{"class":53}," animate",[40,593,70],{"class":56},[40,595,249],{"class":393},[40,597,403],{"class":56},[40,599,600,602,605,607,610,612],{"class":42,"line":112},[40,601,270],{"class":60},[40,603,604],{"class":80}," elapsed",[40,606,276],{"class":60},[40,608,609],{"class":56}," now ",[40,611,286],{"class":60},[40,613,614],{"class":56}," start;\n",[40,616,617,619,622,624,627,630,633,636,638,640,643,646],{"class":42,"line":117},[40,618,270],{"class":60},[40,620,621],{"class":80}," progress",[40,623,276],{"class":60},[40,625,626],{"class":56}," Math.",[40,628,629],{"class":53},"min",[40,631,632],{"class":56},"(elapsed ",[40,634,635],{"class":60},"\u002F",[40,637,322],{"class":80},[40,639,397],{"class":56},[40,641,642],{"class":80},"1",[40,644,645],{"class":56},");  ",[40,647,648],{"class":46},"\u002F\u002F 0 → 1 over 1 second\n",[40,650,651,654,656,659],{"class":42,"line":123},[40,652,653],{"class":56},"    element.style.opacity ",[40,655,243],{"class":60},[40,657,658],{"class":56}," progress;  ",[40,660,661],{"class":46},"\u002F\u002F fade in\n",[40,663,664,667,670,673,676],{"class":42,"line":148},[40,665,666],{"class":60},"    if",[40,668,669],{"class":56}," (progress ",[40,671,672],{"class":60},"\u003C",[40,674,675],{"class":80}," 1",[40,677,403],{"class":56},[40,679,680,683,686],{"class":42,"line":163},[40,681,682],{"class":53},"        requestAnimationFrame",[40,684,685],{"class":56},"(animate);  ",[40,687,688],{"class":46},"\u002F\u002F schedule next frame\n",[40,690,691],{"class":42,"line":197},[40,692,520],{"class":56},[40,694,695],{"class":42,"line":211},[40,696,535],{"class":56},[40,698,699,701,703],{"class":42,"line":217},[40,700,541],{"class":53},[40,702,685],{"class":56},[40,704,705],{"class":46},"\u002F\u002F start the animation\n",[40,707,708],{"class":42,"line":223},[40,709,91],{"emptyLinePlaceholder":90},[40,711,712],{"class":42,"line":228},[40,713,714],{"class":46},"\u002F\u002F ── ❌ Don't use setInterval for animations ──\n",[40,716,717,719,721,723],{"class":42,"line":234},[40,718,25],{"class":53},[40,720,57],{"class":56},[40,722,61],{"class":60},[40,724,264],{"class":56},[40,726,727,730,732,735,738,741,744,747,749,751,754,757],{"class":42,"line":255},[40,728,729],{"class":56},"    element.style.left ",[40,731,243],{"class":60},[40,733,734],{"class":53}," parseInt",[40,736,737],{"class":56},"(element.style.left ",[40,739,740],{"class":60},"||",[40,742,743],{"class":80}," 0",[40,745,746],{"class":56},") ",[40,748,422],{"class":60},[40,750,675],{"class":80},[40,752,753],{"class":60}," +",[40,755,756],{"class":73}," \"px\"",[40,758,759],{"class":56},";\n",[40,761,762,764,767,769],{"class":42,"line":267},[40,763,340],{"class":56},[40,765,766],{"class":80},"16",[40,768,645],{"class":56},[40,770,771],{"class":46},"\u002F\u002F ~60fps, but NOT synced with the display → jank, frame drops\n",[40,773,774],{"class":42,"line":292},[40,775,776],{"class":46},"\u002F\u002F setInterval runs on the event loop (macrotask) — not aligned with paint.\n",[40,778,779],{"class":42,"line":313},[40,780,781],{"class":46},"\u002F\u002F rAF runs just before paint — the browser can optimize rendering.\n",[40,783,784],{"class":42,"line":331},[40,785,91],{"emptyLinePlaceholder":90},[40,787,788],{"class":42,"line":337},[40,789,790],{"class":46},"\u002F\u002F ── Cancel animation ──\n",[40,792,793,795,798,800,803],{"class":42,"line":347},[40,794,237],{"class":60},[40,796,797],{"class":56}," animationId ",[40,799,243],{"class":60},[40,801,802],{"class":53}," requestAnimationFrame",[40,804,805],{"class":56},"(animate);\n",[40,807,808,811,814],{"class":42,"line":353},[40,809,810],{"class":53},"cancelAnimationFrame",[40,812,813],{"class":56},"(animationId);  ",[40,815,816],{"class":46},"\u002F\u002F stop the animation\n",[40,818,819],{"class":42,"line":359},[40,820,91],{"emptyLinePlaceholder":90},[40,822,823],{"class":42,"line":365},[40,824,825],{"class":46},"\u002F\u002F ── requestIdleCallback: run work during idle time (low priority) ──\n",[40,827,828,831,834,837,839,841],{"class":42,"line":371},[40,829,830],{"class":53},"requestIdleCallback",[40,832,833],{"class":56},"((",[40,835,836],{"class":393},"deadline",[40,838,746],{"class":56},[40,840,61],{"class":60},[40,842,264],{"class":56},[40,844,845],{"class":42,"line":376},[40,846,847],{"class":46},"    \u002F\u002F deadline.timeRemaining() → ms left in the current idle period\n",[40,849,850,853,856,859,861,864,866,869,872,875,878,880],{"class":42,"line":382},[40,851,852],{"class":60},"    while",[40,854,855],{"class":56}," (deadline.",[40,857,858],{"class":53},"timeRemaining",[40,860,283],{"class":56},[40,862,863],{"class":60},">",[40,865,743],{"class":80},[40,867,868],{"class":60}," &&",[40,870,871],{"class":56}," tasks.",[40,873,874],{"class":80},"length",[40,876,877],{"class":60}," >",[40,879,743],{"class":80},[40,881,403],{"class":56},[40,883,884,887,890,893],{"class":42,"line":406},[40,885,886],{"class":53},"        processTask",[40,888,889],{"class":56},"(tasks.",[40,891,892],{"class":53},"shift",[40,894,895],{"class":56},"());\n",[40,897,898],{"class":42,"line":428},[40,899,520],{"class":56},[40,901,902,904,907,909,911,913],{"class":42,"line":440},[40,903,666],{"class":60},[40,905,906],{"class":56}," (tasks.",[40,908,874],{"class":80},[40,910,877],{"class":60},[40,912,743],{"class":80},[40,914,403],{"class":56},[40,916,917,920,923],{"class":42,"line":464},[40,918,919],{"class":53},"        requestIdleCallback",[40,921,922],{"class":56},"(processTasks);  ",[40,924,925],{"class":46},"\u002F\u002F continue in next idle period\n",[40,927,928],{"class":42,"line":476},[40,929,520],{"class":56},[40,931,932,935,938,941],{"class":42,"line":490},[40,933,934],{"class":56},"}, { timeout: ",[40,936,937],{"class":80},"2000",[40,939,940],{"class":56}," });  ",[40,942,943],{"class":46},"\u002F\u002F must run within 2s (even if never idle)\n",[15,945,947],{"id":946},"debounce-and-throttle-production-implementations","Debounce and Throttle (Production Implementations)",[28,949,950],{"language":30},[32,951,953],{"className":34,"code":952,"language":30,"meta":36,"style":36},"\u002F\u002F ── Debounce: delay execution until calls stop for `delay` ms ──\n\u002F\u002F Use for: search input, resize, text change (only run after user stops typing)\nfunction debounce(fn, delay, { leading = false, trailing = true } = {}) {\n    let timer = null;\n    let lastArgs = null;\n    return function(...args) {\n        lastArgs = args;\n        \u002F\u002F Leading edge: run immediately on first call (if no pending timer)\n        if (leading && timer === null) {\n            fn.apply(this, args);\n        }\n        clearTimeout(timer);\n        timer = setTimeout(() => {\n            timer = null;\n            if (trailing) fn.apply(this, lastArgs);  \u002F\u002F run with the latest args\n        }, delay);\n    };\n}\nconst search = debounce(query => fetchData(query), 300);\ninput.addEventListener(\"input\", () => search(input.value));\n\u002F\u002F Type \"hello\" quickly: only fires once after 300ms of no typing (with \"hello\")\n\n\u002F\u002F ── Throttle: limit execution to once per `limit` ms ──\n\u002F\u002F Use for: scroll, mousemove, resize (limit the rate of a high-frequency event)\nfunction throttle(fn, limit, { leading = true, trailing = true } = {}) {\n    let lastRun = 0;\n    let timer = null;\n    let lastArgs = null;\n    return function(...args) {\n        const now = Date.now();\n        const remaining = limit - (now - lastRun);\n        lastArgs = args;\n        if (remaining \u003C= 0 || remaining > limit) {\n            \u002F\u002F Enough time passed → run now\n            if (timer) { clearTimeout(timer); timer = null; }\n            lastRun = now;\n            if (leading) fn.apply(this, args);\n        } else if (!timer && trailing) {\n            \u002F\u002F Schedule trailing call\n            timer = setTimeout(() => {\n                lastRun = Date.now();\n                timer = null;\n                fn.apply(this, lastArgs);\n            }, remaining);\n        }\n    };\n}\nconst onScroll = throttle(() => updateIndicator(), 100);\nwindow.addEventListener(\"scroll\", onScroll);\n\u002F\u002F Scroll fast: fires at most every 100ms (not on every pixel)\n\n\u002F\u002F ── Debounced with immediate leading + trailing ──\nconst save = debounce(saveData, 1000, { leading: true, trailing: true });\n\u002F\u002F First call: runs immediately (leading). Subsequent calls within 1s: last one runs after 1s (trailing).\n",[19,954,955,960,965,1009,1023,1036,1054,1064,1069,1089,1105,1110,1118,1134,1145,1165,1170,1175,1179,1210,1233,1238,1242,1247,1252,1290,1303,1315,1327,1341,1356,1378,1386,1409,1414,1434,1444,1459,1484,1489,1504,1518,1530,1545,1551,1556,1561,1566,1593,1609,1615,1620,1626,1657],{"__ignoreMap":36},[40,956,957],{"class":42,"line":43},[40,958,959],{"class":46},"\u002F\u002F ── Debounce: delay execution until calls stop for `delay` ms ──\n",[40,961,962],{"class":42,"line":50},[40,963,964],{"class":46},"\u002F\u002F Use for: search input, resize, text change (only run after user stops typing)\n",[40,966,967,969,972,974,976,978,980,983,986,988,991,993,996,998,1001,1004,1006],{"class":42,"line":87},[40,968,385],{"class":60},[40,970,971],{"class":53}," debounce",[40,973,70],{"class":56},[40,975,394],{"class":393},[40,977,397],{"class":56},[40,979,400],{"class":393},[40,981,982],{"class":56},", { ",[40,984,985],{"class":393},"leading",[40,987,276],{"class":60},[40,989,990],{"class":80}," false",[40,992,397],{"class":56},[40,994,995],{"class":393},"trailing",[40,997,276],{"class":60},[40,999,1000],{"class":80}," true",[40,1002,1003],{"class":56}," } ",[40,1005,243],{"class":60},[40,1007,1008],{"class":56}," {}) {\n",[40,1010,1011,1013,1016,1018,1021],{"class":42,"line":94},[40,1012,409],{"class":60},[40,1014,1015],{"class":56}," timer ",[40,1017,243],{"class":60},[40,1019,1020],{"class":80}," null",[40,1022,759],{"class":56},[40,1024,1025,1027,1030,1032,1034],{"class":42,"line":100},[40,1026,409],{"class":60},[40,1028,1029],{"class":56}," lastArgs ",[40,1031,243],{"class":60},[40,1033,1020],{"class":80},[40,1035,759],{"class":56},[40,1037,1038,1041,1044,1046,1049,1052],{"class":42,"line":106},[40,1039,1040],{"class":60},"    return",[40,1042,1043],{"class":60}," function",[40,1045,70],{"class":56},[40,1047,1048],{"class":60},"...",[40,1050,1051],{"class":393},"args",[40,1053,403],{"class":56},[40,1055,1056,1059,1061],{"class":42,"line":112},[40,1057,1058],{"class":56},"        lastArgs ",[40,1060,243],{"class":60},[40,1062,1063],{"class":56}," args;\n",[40,1065,1066],{"class":42,"line":117},[40,1067,1068],{"class":46},"        \u002F\u002F Leading edge: run immediately on first call (if no pending timer)\n",[40,1070,1071,1074,1077,1080,1082,1085,1087],{"class":42,"line":123},[40,1072,1073],{"class":60},"        if",[40,1075,1076],{"class":56}," (leading ",[40,1078,1079],{"class":60},"&&",[40,1081,1015],{"class":56},[40,1083,1084],{"class":60},"===",[40,1086,1020],{"class":80},[40,1088,403],{"class":56},[40,1090,1091,1094,1097,1099,1102],{"class":42,"line":148},[40,1092,1093],{"class":56},"            fn.",[40,1095,1096],{"class":53},"apply",[40,1098,70],{"class":56},[40,1100,1101],{"class":80},"this",[40,1103,1104],{"class":56},", args);\n",[40,1106,1107],{"class":42,"line":163},[40,1108,1109],{"class":56},"        }\n",[40,1111,1112,1115],{"class":42,"line":197},[40,1113,1114],{"class":53},"        clearTimeout",[40,1116,1117],{"class":56},"(timer);\n",[40,1119,1120,1123,1125,1128,1130,1132],{"class":42,"line":211},[40,1121,1122],{"class":56},"        timer ",[40,1124,243],{"class":60},[40,1126,1127],{"class":53}," setTimeout",[40,1129,57],{"class":56},[40,1131,61],{"class":60},[40,1133,264],{"class":56},[40,1135,1136,1139,1141,1143],{"class":42,"line":217},[40,1137,1138],{"class":56},"            timer ",[40,1140,243],{"class":60},[40,1142,1020],{"class":80},[40,1144,759],{"class":56},[40,1146,1147,1150,1153,1155,1157,1159,1162],{"class":42,"line":223},[40,1148,1149],{"class":60},"            if",[40,1151,1152],{"class":56}," (trailing) fn.",[40,1154,1096],{"class":53},[40,1156,70],{"class":56},[40,1158,1101],{"class":80},[40,1160,1161],{"class":56},", lastArgs);  ",[40,1163,1164],{"class":46},"\u002F\u002F run with the latest args\n",[40,1166,1167],{"class":42,"line":228},[40,1168,1169],{"class":56},"        }, delay);\n",[40,1171,1172],{"class":42,"line":234},[40,1173,1174],{"class":56},"    };\n",[40,1176,1177],{"class":42,"line":255},[40,1178,535],{"class":56},[40,1180,1181,1184,1187,1189,1191,1193,1196,1199,1202,1205,1208],{"class":42,"line":267},[40,1182,1183],{"class":60},"const",[40,1185,1186],{"class":80}," search",[40,1188,276],{"class":60},[40,1190,971],{"class":53},[40,1192,70],{"class":56},[40,1194,1195],{"class":393},"query",[40,1197,1198],{"class":60}," =>",[40,1200,1201],{"class":53}," fetchData",[40,1203,1204],{"class":56},"(query), ",[40,1206,1207],{"class":80},"300",[40,1209,84],{"class":56},[40,1211,1212,1215,1218,1220,1223,1226,1228,1230],{"class":42,"line":292},[40,1213,1214],{"class":56},"input.",[40,1216,1217],{"class":53},"addEventListener",[40,1219,70],{"class":56},[40,1221,1222],{"class":73},"\"input\"",[40,1224,1225],{"class":56},", () ",[40,1227,61],{"class":60},[40,1229,1186],{"class":53},[40,1231,1232],{"class":56},"(input.value));\n",[40,1234,1235],{"class":42,"line":313},[40,1236,1237],{"class":46},"\u002F\u002F Type \"hello\" quickly: only fires once after 300ms of no typing (with \"hello\")\n",[40,1239,1240],{"class":42,"line":331},[40,1241,91],{"emptyLinePlaceholder":90},[40,1243,1244],{"class":42,"line":337},[40,1245,1246],{"class":46},"\u002F\u002F ── Throttle: limit execution to once per `limit` ms ──\n",[40,1248,1249],{"class":42,"line":347},[40,1250,1251],{"class":46},"\u002F\u002F Use for: scroll, mousemove, resize (limit the rate of a high-frequency event)\n",[40,1253,1254,1256,1259,1261,1263,1265,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286,1288],{"class":42,"line":353},[40,1255,385],{"class":60},[40,1257,1258],{"class":53}," throttle",[40,1260,70],{"class":56},[40,1262,394],{"class":393},[40,1264,397],{"class":56},[40,1266,1267],{"class":393},"limit",[40,1269,982],{"class":56},[40,1271,985],{"class":393},[40,1273,276],{"class":60},[40,1275,1000],{"class":80},[40,1277,397],{"class":56},[40,1279,995],{"class":393},[40,1281,276],{"class":60},[40,1283,1000],{"class":80},[40,1285,1003],{"class":56},[40,1287,243],{"class":60},[40,1289,1008],{"class":56},[40,1291,1292,1294,1297,1299,1301],{"class":42,"line":359},[40,1293,409],{"class":60},[40,1295,1296],{"class":56}," lastRun ",[40,1298,243],{"class":60},[40,1300,743],{"class":80},[40,1302,759],{"class":56},[40,1304,1305,1307,1309,1311,1313],{"class":42,"line":365},[40,1306,409],{"class":60},[40,1308,1015],{"class":56},[40,1310,243],{"class":60},[40,1312,1020],{"class":80},[40,1314,759],{"class":56},[40,1316,1317,1319,1321,1323,1325],{"class":42,"line":371},[40,1318,409],{"class":60},[40,1320,1029],{"class":56},[40,1322,243],{"class":60},[40,1324,1020],{"class":80},[40,1326,759],{"class":56},[40,1328,1329,1331,1333,1335,1337,1339],{"class":42,"line":376},[40,1330,1040],{"class":60},[40,1332,1043],{"class":60},[40,1334,70],{"class":56},[40,1336,1048],{"class":60},[40,1338,1051],{"class":393},[40,1340,403],{"class":56},[40,1342,1343,1345,1348,1350,1352,1354],{"class":42,"line":382},[40,1344,443],{"class":60},[40,1346,1347],{"class":80}," now",[40,1349,276],{"class":60},[40,1351,246],{"class":56},[40,1353,249],{"class":53},[40,1355,252],{"class":56},[40,1357,1358,1360,1363,1365,1368,1370,1373,1375],{"class":42,"line":406},[40,1359,443],{"class":60},[40,1361,1362],{"class":80}," remaining",[40,1364,276],{"class":60},[40,1366,1367],{"class":56}," limit ",[40,1369,286],{"class":60},[40,1371,1372],{"class":56}," (now ",[40,1374,286],{"class":60},[40,1376,1377],{"class":56}," lastRun);\n",[40,1379,1380,1382,1384],{"class":42,"line":428},[40,1381,1058],{"class":56},[40,1383,243],{"class":60},[40,1385,1063],{"class":56},[40,1387,1388,1390,1393,1396,1398,1401,1404,1406],{"class":42,"line":440},[40,1389,1073],{"class":60},[40,1391,1392],{"class":56}," (remaining ",[40,1394,1395],{"class":60},"\u003C=",[40,1397,743],{"class":80},[40,1399,1400],{"class":60}," ||",[40,1402,1403],{"class":56}," remaining ",[40,1405,863],{"class":60},[40,1407,1408],{"class":56}," limit) {\n",[40,1410,1411],{"class":42,"line":464},[40,1412,1413],{"class":46},"            \u002F\u002F Enough time passed → run now\n",[40,1415,1416,1418,1421,1424,1427,1429,1431],{"class":42,"line":476},[40,1417,1149],{"class":60},[40,1419,1420],{"class":56}," (timer) { ",[40,1422,1423],{"class":53},"clearTimeout",[40,1425,1426],{"class":56},"(timer); timer ",[40,1428,243],{"class":60},[40,1430,1020],{"class":80},[40,1432,1433],{"class":56},"; }\n",[40,1435,1436,1439,1441],{"class":42,"line":490},[40,1437,1438],{"class":56},"            lastRun ",[40,1440,243],{"class":60},[40,1442,1443],{"class":56}," now;\n",[40,1445,1446,1448,1451,1453,1455,1457],{"class":42,"line":517},[40,1447,1149],{"class":60},[40,1449,1450],{"class":56}," (leading) fn.",[40,1452,1096],{"class":53},[40,1454,70],{"class":56},[40,1456,1101],{"class":80},[40,1458,1104],{"class":56},[40,1460,1461,1464,1467,1470,1473,1476,1479,1481],{"class":42,"line":523},[40,1462,1463],{"class":56},"        } ",[40,1465,1466],{"class":60},"else",[40,1468,1469],{"class":60}," if",[40,1471,1472],{"class":56}," (",[40,1474,1475],{"class":60},"!",[40,1477,1478],{"class":56},"timer ",[40,1480,1079],{"class":60},[40,1482,1483],{"class":56}," trailing) {\n",[40,1485,1486],{"class":42,"line":532},[40,1487,1488],{"class":46},"            \u002F\u002F Schedule trailing call\n",[40,1490,1492,1494,1496,1498,1500,1502],{"class":42,"line":1491},40,[40,1493,1138],{"class":56},[40,1495,243],{"class":60},[40,1497,1127],{"class":53},[40,1499,57],{"class":56},[40,1501,61],{"class":60},[40,1503,264],{"class":56},[40,1505,1507,1510,1512,1514,1516],{"class":42,"line":1506},41,[40,1508,1509],{"class":56},"                lastRun ",[40,1511,243],{"class":60},[40,1513,246],{"class":56},[40,1515,249],{"class":53},[40,1517,252],{"class":56},[40,1519,1521,1524,1526,1528],{"class":42,"line":1520},42,[40,1522,1523],{"class":56},"                timer ",[40,1525,243],{"class":60},[40,1527,1020],{"class":80},[40,1529,759],{"class":56},[40,1531,1533,1536,1538,1540,1542],{"class":42,"line":1532},43,[40,1534,1535],{"class":56},"                fn.",[40,1537,1096],{"class":53},[40,1539,70],{"class":56},[40,1541,1101],{"class":80},[40,1543,1544],{"class":56},", lastArgs);\n",[40,1546,1548],{"class":42,"line":1547},44,[40,1549,1550],{"class":56},"            }, remaining);\n",[40,1552,1554],{"class":42,"line":1553},45,[40,1555,1109],{"class":56},[40,1557,1559],{"class":42,"line":1558},46,[40,1560,1174],{"class":56},[40,1562,1564],{"class":42,"line":1563},47,[40,1565,535],{"class":56},[40,1567,1569,1571,1574,1576,1578,1580,1582,1585,1588,1591],{"class":42,"line":1568},48,[40,1570,1183],{"class":60},[40,1572,1573],{"class":80}," onScroll",[40,1575,276],{"class":60},[40,1577,1258],{"class":53},[40,1579,57],{"class":56},[40,1581,61],{"class":60},[40,1583,1584],{"class":53}," updateIndicator",[40,1586,1587],{"class":56},"(), ",[40,1589,1590],{"class":80},"100",[40,1592,84],{"class":56},[40,1594,1596,1599,1601,1603,1606],{"class":42,"line":1595},49,[40,1597,1598],{"class":56},"window.",[40,1600,1217],{"class":53},[40,1602,70],{"class":56},[40,1604,1605],{"class":73},"\"scroll\"",[40,1607,1608],{"class":56},", onScroll);\n",[40,1610,1612],{"class":42,"line":1611},50,[40,1613,1614],{"class":46},"\u002F\u002F Scroll fast: fires at most every 100ms (not on every pixel)\n",[40,1616,1618],{"class":42,"line":1617},51,[40,1619,91],{"emptyLinePlaceholder":90},[40,1621,1623],{"class":42,"line":1622},52,[40,1624,1625],{"class":46},"\u002F\u002F ── Debounced with immediate leading + trailing ──\n",[40,1627,1629,1631,1634,1636,1638,1641,1643,1646,1649,1652,1654],{"class":42,"line":1628},53,[40,1630,1183],{"class":60},[40,1632,1633],{"class":80}," save",[40,1635,276],{"class":60},[40,1637,971],{"class":53},[40,1639,1640],{"class":56},"(saveData, ",[40,1642,81],{"class":80},[40,1644,1645],{"class":56},", { leading: ",[40,1647,1648],{"class":80},"true",[40,1650,1651],{"class":56},", trailing: ",[40,1653,1648],{"class":80},[40,1655,1656],{"class":56}," });\n",[40,1658,1660],{"class":42,"line":1659},54,[40,1661,1662],{"class":46},"\u002F\u002F First call: runs immediately (leading). Subsequent calls within 1s: last one runs after 1s (trailing).\n",[15,1664,1666],{"id":1665},"anti-pattern-uncleaned-timers-memory-leaks","Anti-Pattern: Uncleaned Timers (Memory Leaks)",[28,1668,1669],{"language":30},[32,1670,1672],{"className":34,"code":1671,"language":30,"meta":36,"style":36},"\u002F\u002F ❌ NAIVE — timers keep running after the component is removed (memory leak)\nfunction setupCarousel() {\n    setInterval(() => {\n        nextSlide();  \u002F\u002F runs forever — even after the carousel is removed\n    }, 5000);\n}\n\u002F\u002F When the carousel is removed from the DOM, the interval still runs.\n\u002F\u002F If the callback references DOM elements, they can't be GC'd → memory leak.\n\n\u002F\u002F ✅ CORRECT — store the timer ID and clear it on cleanup\nfunction setupCarousel() {\n    const intervalId = setInterval(nextSlide, 5000);\n    return () => clearInterval(intervalId);  \u002F\u002F return cleanup function\n}\nconst cleanup = setupCarousel();\n\u002F\u002F Later (when the carousel is removed):\ncleanup();  \u002F\u002F clearInterval — timer stops, references released, GC can collect\n\n\u002F\u002F ✅ BEST — use AbortController (modern, handles multiple timers + events)\nfunction setupCarousel(signal) {\n    const intervalId = setInterval(nextSlide, 5000);\n    signal.addEventListener(\"abort\", () => clearInterval(intervalId));\n}\nconst controller = new AbortController();\nsetupCarousel(controller.signal);\n\u002F\u002F Later: controller.abort();  \u002F\u002F stops the timer (and any other resources on the same signal)\n\n\u002F\u002F ── clearTimeout for setTimeout (same pattern) ──\nconst timeoutId = setTimeout(() => console.log(\"runs once\"), 5000);\nclearTimeout(timeoutId);  \u002F\u002F cancel before it runs\n",[19,1673,1674,1679,1688,1699,1710,1720,1724,1729,1734,1738,1743,1751,1770,1788,1792,1805,1810,1820,1824,1829,1842,1858,1879,1883,1900,1908,1916,1920,1925,1955],{"__ignoreMap":36},[40,1675,1676],{"class":42,"line":43},[40,1677,1678],{"class":46},"\u002F\u002F ❌ NAIVE — timers keep running after the component is removed (memory leak)\n",[40,1680,1681,1683,1686],{"class":42,"line":50},[40,1682,385],{"class":60},[40,1684,1685],{"class":53}," setupCarousel",[40,1687,437],{"class":56},[40,1689,1690,1693,1695,1697],{"class":42,"line":87},[40,1691,1692],{"class":53},"    setInterval",[40,1694,57],{"class":56},[40,1696,61],{"class":60},[40,1698,264],{"class":56},[40,1700,1701,1704,1707],{"class":42,"line":94},[40,1702,1703],{"class":53},"        nextSlide",[40,1705,1706],{"class":56},"();  ",[40,1708,1709],{"class":46},"\u002F\u002F runs forever — even after the carousel is removed\n",[40,1711,1712,1715,1718],{"class":42,"line":100},[40,1713,1714],{"class":56},"    }, ",[40,1716,1717],{"class":80},"5000",[40,1719,84],{"class":56},[40,1721,1722],{"class":42,"line":106},[40,1723,535],{"class":56},[40,1725,1726],{"class":42,"line":112},[40,1727,1728],{"class":46},"\u002F\u002F When the carousel is removed from the DOM, the interval still runs.\n",[40,1730,1731],{"class":42,"line":117},[40,1732,1733],{"class":46},"\u002F\u002F If the callback references DOM elements, they can't be GC'd → memory leak.\n",[40,1735,1736],{"class":42,"line":123},[40,1737,91],{"emptyLinePlaceholder":90},[40,1739,1740],{"class":42,"line":148},[40,1741,1742],{"class":46},"\u002F\u002F ✅ CORRECT — store the timer ID and clear it on cleanup\n",[40,1744,1745,1747,1749],{"class":42,"line":163},[40,1746,385],{"class":60},[40,1748,1685],{"class":53},[40,1750,437],{"class":56},[40,1752,1753,1755,1758,1760,1763,1766,1768],{"class":42,"line":197},[40,1754,270],{"class":60},[40,1756,1757],{"class":80}," intervalId",[40,1759,276],{"class":60},[40,1761,1762],{"class":53}," setInterval",[40,1764,1765],{"class":56},"(nextSlide, ",[40,1767,1717],{"class":80},[40,1769,84],{"class":56},[40,1771,1772,1774,1777,1779,1782,1785],{"class":42,"line":211},[40,1773,1040],{"class":60},[40,1775,1776],{"class":56}," () ",[40,1778,61],{"class":60},[40,1780,1781],{"class":53}," clearInterval",[40,1783,1784],{"class":56},"(intervalId);  ",[40,1786,1787],{"class":46},"\u002F\u002F return cleanup function\n",[40,1789,1790],{"class":42,"line":217},[40,1791,535],{"class":56},[40,1793,1794,1796,1799,1801,1803],{"class":42,"line":223},[40,1795,1183],{"class":60},[40,1797,1798],{"class":80}," cleanup",[40,1800,276],{"class":60},[40,1802,1685],{"class":53},[40,1804,252],{"class":56},[40,1806,1807],{"class":42,"line":228},[40,1808,1809],{"class":46},"\u002F\u002F Later (when the carousel is removed):\n",[40,1811,1812,1815,1817],{"class":42,"line":234},[40,1813,1814],{"class":53},"cleanup",[40,1816,1706],{"class":56},[40,1818,1819],{"class":46},"\u002F\u002F clearInterval — timer stops, references released, GC can collect\n",[40,1821,1822],{"class":42,"line":255},[40,1823,91],{"emptyLinePlaceholder":90},[40,1825,1826],{"class":42,"line":267},[40,1827,1828],{"class":46},"\u002F\u002F ✅ BEST — use AbortController (modern, handles multiple timers + events)\n",[40,1830,1831,1833,1835,1837,1840],{"class":42,"line":292},[40,1832,385],{"class":60},[40,1834,1685],{"class":53},[40,1836,70],{"class":56},[40,1838,1839],{"class":393},"signal",[40,1841,403],{"class":56},[40,1843,1844,1846,1848,1850,1852,1854,1856],{"class":42,"line":313},[40,1845,270],{"class":60},[40,1847,1757],{"class":80},[40,1849,276],{"class":60},[40,1851,1762],{"class":53},[40,1853,1765],{"class":56},[40,1855,1717],{"class":80},[40,1857,84],{"class":56},[40,1859,1860,1863,1865,1867,1870,1872,1874,1876],{"class":42,"line":331},[40,1861,1862],{"class":56},"    signal.",[40,1864,1217],{"class":53},[40,1866,70],{"class":56},[40,1868,1869],{"class":73},"\"abort\"",[40,1871,1225],{"class":56},[40,1873,61],{"class":60},[40,1875,1781],{"class":53},[40,1877,1878],{"class":56},"(intervalId));\n",[40,1880,1881],{"class":42,"line":337},[40,1882,535],{"class":56},[40,1884,1885,1887,1890,1892,1895,1898],{"class":42,"line":347},[40,1886,1183],{"class":60},[40,1888,1889],{"class":80}," controller",[40,1891,276],{"class":60},[40,1893,1894],{"class":60}," new",[40,1896,1897],{"class":53}," AbortController",[40,1899,252],{"class":56},[40,1901,1902,1905],{"class":42,"line":353},[40,1903,1904],{"class":53},"setupCarousel",[40,1906,1907],{"class":56},"(controller.signal);\n",[40,1909,1910,1913],{"class":42,"line":359},[40,1911,1912],{"class":46},"\u002F\u002F Later: controller.abort();",[40,1914,1915],{"class":46},"  \u002F\u002F stops the timer (and any other resources on the same signal)\n",[40,1917,1918],{"class":42,"line":365},[40,1919,91],{"emptyLinePlaceholder":90},[40,1921,1922],{"class":42,"line":371},[40,1923,1924],{"class":46},"\u002F\u002F ── clearTimeout for setTimeout (same pattern) ──\n",[40,1926,1927,1929,1932,1934,1936,1938,1940,1942,1944,1946,1949,1951,1953],{"class":42,"line":376},[40,1928,1183],{"class":60},[40,1930,1931],{"class":80}," timeoutId",[40,1933,276],{"class":60},[40,1935,1127],{"class":53},[40,1937,57],{"class":56},[40,1939,61],{"class":60},[40,1941,64],{"class":56},[40,1943,67],{"class":53},[40,1945,70],{"class":56},[40,1947,1948],{"class":73},"\"runs once\"",[40,1950,77],{"class":56},[40,1952,1717],{"class":80},[40,1954,84],{"class":56},[40,1956,1957,1959,1962],{"class":42,"line":382},[40,1958,1423],{"class":53},[40,1960,1961],{"class":56},"(timeoutId);  ",[40,1963,1964],{"class":46},"\u002F\u002F cancel before it runs\n",[15,1966,1968],{"id":1967},"tips-tricks","💡 Tips & Tricks",[28,1970,1971],{"language":30},[32,1972,1974],{"className":34,"code":1973,"language":30,"meta":36,"style":36},"\u002F\u002F ── `await setTimeout` (promisified) ──\nconst delay = (ms) => new Promise(r => setTimeout(r, ms));\nawait delay(2000);  \u002F\u002F pause for 2s\n\n\u002F\u002F ── Polling with exponential backoff ──\nasync function poll(url, { maxAttempts = 10, baseDelay = 1000 } = {}) {\n    for (let i = 0; i \u003C maxAttempts; i++) {\n        try {\n            const response = await fetch(url);\n            if (response.ok) return await response.json();\n        } catch (e) {\n            \u002F\u002F network error — keep polling\n        }\n        await delay(baseDelay * 2 ** i);  \u002F\u002F 1s, 2s, 4s, 8s, ...\n    }\n    throw new Error(\"polling failed after \" + maxAttempts + \" attempts\");\n}\n\n\u002F\u002F ── scheduler.yield (Chrome 129+) — yield to event loop with higher priority than setTimeout ──\nasync function yieldToEventLoop() {\n    if (typeof scheduler !== \"undefined\" && scheduler.yield) {\n        await scheduler.yield();  \u002F\u002F runs sooner than setTimeout(0)\n    } else {\n        await delay(0);  \u002F\u002F fallback to setTimeout\n    }\n}\n\n\u002F\u002F ── requestAnimationFrame for non-animation work (sync with frame) ──\nfunction runBeforePaint(fn) {\n    return new Promise(resolve => {\n        requestAnimationFrame(() => { fn(); resolve(); });\n    });\n}\n\n\u002F\u002F ── setTimeout(fn, 0) vs queueMicrotask(fn) ──\n\u002F\u002F setTimeout: macrotask (runs after microtasks, slower)\n\u002F\u002F queueMicrotask: microtask (runs before macrotasks, faster)\n\u002F\u002F For \"run after current code\": queueMicrotask is faster (doesn't wait for the next tick).\n",[19,1975,1976,1981,2016,2032,2036,2041,2081,2110,2117,2136,2156,2166,2171,2175,2200,2204,2231,2235,2239,2244,2255,2278,2293,2302,2317,2321,2325,2329,2334,2347,2363,2384,2389,2393,2397,2402,2407,2412],{"__ignoreMap":36},[40,1977,1978],{"class":42,"line":43},[40,1979,1980],{"class":46},"\u002F\u002F ── `await setTimeout` (promisified) ──\n",[40,1982,1983,1985,1988,1990,1992,1995,1997,1999,2001,2004,2006,2009,2011,2013],{"class":42,"line":50},[40,1984,1183],{"class":60},[40,1986,1987],{"class":53}," delay",[40,1989,276],{"class":60},[40,1991,1472],{"class":56},[40,1993,1994],{"class":393},"ms",[40,1996,746],{"class":56},[40,1998,61],{"class":60},[40,2000,1894],{"class":60},[40,2002,2003],{"class":80}," Promise",[40,2005,70],{"class":56},[40,2007,2008],{"class":393},"r",[40,2010,1198],{"class":60},[40,2012,1127],{"class":53},[40,2014,2015],{"class":56},"(r, ms));\n",[40,2017,2018,2021,2023,2025,2027,2029],{"class":42,"line":87},[40,2019,2020],{"class":60},"await",[40,2022,1987],{"class":53},[40,2024,70],{"class":56},[40,2026,937],{"class":80},[40,2028,645],{"class":56},[40,2030,2031],{"class":46},"\u002F\u002F pause for 2s\n",[40,2033,2034],{"class":42,"line":94},[40,2035,91],{"emptyLinePlaceholder":90},[40,2037,2038],{"class":42,"line":100},[40,2039,2040],{"class":46},"\u002F\u002F ── Polling with exponential backoff ──\n",[40,2042,2043,2046,2048,2051,2053,2056,2058,2061,2063,2066,2068,2071,2073,2075,2077,2079],{"class":42,"line":106},[40,2044,2045],{"class":60},"async",[40,2047,1043],{"class":60},[40,2049,2050],{"class":53}," poll",[40,2052,70],{"class":56},[40,2054,2055],{"class":393},"url",[40,2057,982],{"class":56},[40,2059,2060],{"class":393},"maxAttempts",[40,2062,276],{"class":60},[40,2064,2065],{"class":80}," 10",[40,2067,397],{"class":56},[40,2069,2070],{"class":393},"baseDelay",[40,2072,276],{"class":60},[40,2074,322],{"class":80},[40,2076,1003],{"class":56},[40,2078,243],{"class":60},[40,2080,1008],{"class":56},[40,2082,2083,2086,2088,2090,2093,2095,2097,2100,2102,2105,2108],{"class":42,"line":112},[40,2084,2085],{"class":60},"    for",[40,2087,1472],{"class":56},[40,2089,237],{"class":60},[40,2091,2092],{"class":56}," i ",[40,2094,243],{"class":60},[40,2096,743],{"class":80},[40,2098,2099],{"class":56},"; i ",[40,2101,672],{"class":60},[40,2103,2104],{"class":56}," maxAttempts; i",[40,2106,2107],{"class":60},"++",[40,2109,403],{"class":56},[40,2111,2112,2115],{"class":42,"line":117},[40,2113,2114],{"class":60},"        try",[40,2116,264],{"class":56},[40,2118,2119,2122,2125,2127,2130,2133],{"class":42,"line":123},[40,2120,2121],{"class":60},"            const",[40,2123,2124],{"class":80}," response",[40,2126,276],{"class":60},[40,2128,2129],{"class":60}," await",[40,2131,2132],{"class":53}," fetch",[40,2134,2135],{"class":56},"(url);\n",[40,2137,2138,2140,2143,2146,2148,2151,2154],{"class":42,"line":148},[40,2139,1149],{"class":60},[40,2141,2142],{"class":56}," (response.ok) ",[40,2144,2145],{"class":60},"return",[40,2147,2129],{"class":60},[40,2149,2150],{"class":56}," response.",[40,2152,2153],{"class":53},"json",[40,2155,252],{"class":56},[40,2157,2158,2160,2163],{"class":42,"line":163},[40,2159,1463],{"class":56},[40,2161,2162],{"class":60},"catch",[40,2164,2165],{"class":56}," (e) {\n",[40,2167,2168],{"class":42,"line":197},[40,2169,2170],{"class":46},"            \u002F\u002F network error — keep polling\n",[40,2172,2173],{"class":42,"line":211},[40,2174,1109],{"class":56},[40,2176,2177,2180,2182,2185,2188,2191,2194,2197],{"class":42,"line":217},[40,2178,2179],{"class":60},"        await",[40,2181,1987],{"class":53},[40,2183,2184],{"class":56},"(baseDelay ",[40,2186,2187],{"class":60},"*",[40,2189,2190],{"class":80}," 2",[40,2192,2193],{"class":60}," **",[40,2195,2196],{"class":56}," i);  ",[40,2198,2199],{"class":46},"\u002F\u002F 1s, 2s, 4s, 8s, ...\n",[40,2201,2202],{"class":42,"line":223},[40,2203,520],{"class":56},[40,2205,2206,2209,2211,2214,2216,2219,2221,2224,2226,2229],{"class":42,"line":228},[40,2207,2208],{"class":60},"    throw",[40,2210,1894],{"class":60},[40,2212,2213],{"class":53}," Error",[40,2215,70],{"class":56},[40,2217,2218],{"class":73},"\"polling failed after \"",[40,2220,753],{"class":60},[40,2222,2223],{"class":56}," maxAttempts ",[40,2225,422],{"class":60},[40,2227,2228],{"class":73}," \" attempts\"",[40,2230,84],{"class":56},[40,2232,2233],{"class":42,"line":234},[40,2234,535],{"class":56},[40,2236,2237],{"class":42,"line":255},[40,2238,91],{"emptyLinePlaceholder":90},[40,2240,2241],{"class":42,"line":267},[40,2242,2243],{"class":46},"\u002F\u002F ── scheduler.yield (Chrome 129+) — yield to event loop with higher priority than setTimeout ──\n",[40,2245,2246,2248,2250,2253],{"class":42,"line":292},[40,2247,2045],{"class":60},[40,2249,1043],{"class":60},[40,2251,2252],{"class":53}," yieldToEventLoop",[40,2254,437],{"class":56},[40,2256,2257,2259,2261,2264,2267,2270,2273,2275],{"class":42,"line":313},[40,2258,666],{"class":60},[40,2260,1472],{"class":56},[40,2262,2263],{"class":60},"typeof",[40,2265,2266],{"class":56}," scheduler ",[40,2268,2269],{"class":60},"!==",[40,2271,2272],{"class":73}," \"undefined\"",[40,2274,868],{"class":60},[40,2276,2277],{"class":56}," scheduler.yield) {\n",[40,2279,2280,2282,2285,2288,2290],{"class":42,"line":331},[40,2281,2179],{"class":60},[40,2283,2284],{"class":56}," scheduler.",[40,2286,2287],{"class":53},"yield",[40,2289,1706],{"class":56},[40,2291,2292],{"class":46},"\u002F\u002F runs sooner than setTimeout(0)\n",[40,2294,2295,2298,2300],{"class":42,"line":337},[40,2296,2297],{"class":56},"    } ",[40,2299,1466],{"class":60},[40,2301,264],{"class":56},[40,2303,2304,2306,2308,2310,2312,2314],{"class":42,"line":347},[40,2305,2179],{"class":60},[40,2307,1987],{"class":53},[40,2309,70],{"class":56},[40,2311,143],{"class":80},[40,2313,645],{"class":56},[40,2315,2316],{"class":46},"\u002F\u002F fallback to setTimeout\n",[40,2318,2319],{"class":42,"line":353},[40,2320,520],{"class":56},[40,2322,2323],{"class":42,"line":359},[40,2324,535],{"class":56},[40,2326,2327],{"class":42,"line":365},[40,2328,91],{"emptyLinePlaceholder":90},[40,2330,2331],{"class":42,"line":371},[40,2332,2333],{"class":46},"\u002F\u002F ── requestAnimationFrame for non-animation work (sync with frame) ──\n",[40,2335,2336,2338,2341,2343,2345],{"class":42,"line":376},[40,2337,385],{"class":60},[40,2339,2340],{"class":53}," runBeforePaint",[40,2342,70],{"class":56},[40,2344,394],{"class":393},[40,2346,403],{"class":56},[40,2348,2349,2351,2353,2355,2357,2359,2361],{"class":42,"line":382},[40,2350,1040],{"class":60},[40,2352,1894],{"class":60},[40,2354,2003],{"class":80},[40,2356,70],{"class":56},[40,2358,172],{"class":393},[40,2360,1198],{"class":60},[40,2362,264],{"class":56},[40,2364,2365,2367,2369,2371,2374,2376,2379,2381],{"class":42,"line":406},[40,2366,682],{"class":53},[40,2368,57],{"class":56},[40,2370,61],{"class":60},[40,2372,2373],{"class":56}," { ",[40,2375,394],{"class":53},[40,2377,2378],{"class":56},"(); ",[40,2380,172],{"class":53},[40,2382,2383],{"class":56},"(); });\n",[40,2385,2386],{"class":42,"line":428},[40,2387,2388],{"class":56},"    });\n",[40,2390,2391],{"class":42,"line":440},[40,2392,535],{"class":56},[40,2394,2395],{"class":42,"line":464},[40,2396,91],{"emptyLinePlaceholder":90},[40,2398,2399],{"class":42,"line":476},[40,2400,2401],{"class":46},"\u002F\u002F ── setTimeout(fn, 0) vs queueMicrotask(fn) ──\n",[40,2403,2404],{"class":42,"line":490},[40,2405,2406],{"class":46},"\u002F\u002F setTimeout: macrotask (runs after microtasks, slower)\n",[40,2408,2409],{"class":42,"line":517},[40,2410,2411],{"class":46},"\u002F\u002F queueMicrotask: microtask (runs before macrotasks, faster)\n",[40,2413,2414],{"class":42,"line":523},[40,2415,2416],{"class":46},"\u002F\u002F For \"run after current code\": queueMicrotask is faster (doesn't wait for the next tick).\n",[15,2418,2420],{"id":2419},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[28,2422,2423],{"language":30},[32,2424,2426],{"className":34,"code":2425,"language":30,"meta":36,"style":36},"\u002F\u002F ── setTimeout minimum delay is ~4ms (after 5 nested timers) ──\n\u002F\u002F In browsers: setTimeout(fn, 0) is clamped to ~4ms after 5 nested calls.\n\u002F\u002F setTimeout(fn, 0)  → ~4ms, not 0ms.\n\u002F\u002F queueMicrotask(fn) runs sooner (microtask — before the next macrotask).\n\n\u002F\u002F ── `this` inside setTimeout callback is global (not the enclosing object) ──\nconst obj = { name: \"Alice\", greet() { setTimeout(function() { console.log(this.name); }, 0); } };\nobj.greet();  \u002F\u002F undefined (this is global, not obj)\n\u002F\u002F Fix: arrow function (lexical this) or .bind(this)\nobj.greet = function() { setTimeout(() => console.log(this.name), 0); };\nobj.greet();  \u002F\u002F \"Alice\" (arrow has lexical this)\n\n\u002F\u002F ── setInterval doesn't wait for the callback to finish ──\nsetInterval(async () => {\n    await longRunningTask();  \u002F\u002F takes 3s, interval is 1s\n    \u002F\u002F ⚠️ intervals pile up! After 3s, 3 pending callbacks queue up.\n    \u002F\u002F Fix: use self-scheduling setTimeout (recursive) instead of setInterval:\n}, 1000);\n\n\u002F\u002F ── Timer callbacks are macrotasks (run after all microtasks) ──\nsetTimeout(() => console.log(\"timer\"), 0);\nPromise.resolve().then(() => console.log(\"promise\"));\n\u002F\u002F Output: promise, timer (microtask runs first)\n\n\u002F\u002F ── Timers don't fire when the tab is hidden (throttled to 1\u002Fsec) ──\n\u002F\u002F In background tabs, setTimeout\u002FsetInterval are throttled to ~1\u002Fsec (battery saving).\n\u002F\u002F Use Web Workers for timers that must run at full speed in background.\n\n\u002F\u002F ── clearInterval doesn't cancel a callback that's already executing ──\n\u002F\u002F If the callback is running when you call clearInterval, it finishes.\n\u002F\u002F clearInterval only prevents FUTURE invocations.\n",[19,2427,2428,2433,2438,2443,2448,2452,2457,2503,2515,2520,2554,2565,2569,2574,2588,2601,2606,2611,2619,2623,2628,2651,2678,2683,2687,2692,2697,2702,2706,2711,2716],{"__ignoreMap":36},[40,2429,2430],{"class":42,"line":43},[40,2431,2432],{"class":46},"\u002F\u002F ── setTimeout minimum delay is ~4ms (after 5 nested timers) ──\n",[40,2434,2435],{"class":42,"line":50},[40,2436,2437],{"class":46},"\u002F\u002F In browsers: setTimeout(fn, 0) is clamped to ~4ms after 5 nested calls.\n",[40,2439,2440],{"class":42,"line":87},[40,2441,2442],{"class":46},"\u002F\u002F setTimeout(fn, 0)  → ~4ms, not 0ms.\n",[40,2444,2445],{"class":42,"line":94},[40,2446,2447],{"class":46},"\u002F\u002F queueMicrotask(fn) runs sooner (microtask — before the next macrotask).\n",[40,2449,2450],{"class":42,"line":100},[40,2451,91],{"emptyLinePlaceholder":90},[40,2453,2454],{"class":42,"line":106},[40,2455,2456],{"class":46},"\u002F\u002F ── `this` inside setTimeout callback is global (not the enclosing object) ──\n",[40,2458,2459,2461,2464,2466,2469,2472,2474,2477,2480,2482,2484,2486,2489,2491,2493,2495,2498,2500],{"class":42,"line":112},[40,2460,1183],{"class":60},[40,2462,2463],{"class":80}," obj",[40,2465,276],{"class":60},[40,2467,2468],{"class":56}," { name: ",[40,2470,2471],{"class":73},"\"Alice\"",[40,2473,397],{"class":56},[40,2475,2476],{"class":53},"greet",[40,2478,2479],{"class":56},"() { ",[40,2481,21],{"class":53},[40,2483,70],{"class":56},[40,2485,385],{"class":60},[40,2487,2488],{"class":56},"() { console.",[40,2490,67],{"class":53},[40,2492,70],{"class":56},[40,2494,1101],{"class":80},[40,2496,2497],{"class":56},".name); }, ",[40,2499,143],{"class":80},[40,2501,2502],{"class":56},"); } };\n",[40,2504,2505,2508,2510,2512],{"class":42,"line":117},[40,2506,2507],{"class":56},"obj.",[40,2509,2476],{"class":53},[40,2511,1706],{"class":56},[40,2513,2514],{"class":46},"\u002F\u002F undefined (this is global, not obj)\n",[40,2516,2517],{"class":42,"line":123},[40,2518,2519],{"class":46},"\u002F\u002F Fix: arrow function (lexical this) or .bind(this)\n",[40,2521,2522,2524,2526,2528,2530,2532,2534,2536,2538,2540,2542,2544,2546,2549,2551],{"class":42,"line":148},[40,2523,2507],{"class":56},[40,2525,2476],{"class":53},[40,2527,276],{"class":60},[40,2529,1043],{"class":60},[40,2531,2479],{"class":56},[40,2533,21],{"class":53},[40,2535,57],{"class":56},[40,2537,61],{"class":60},[40,2539,64],{"class":56},[40,2541,67],{"class":53},[40,2543,70],{"class":56},[40,2545,1101],{"class":80},[40,2547,2548],{"class":56},".name), ",[40,2550,143],{"class":80},[40,2552,2553],{"class":56},"); };\n",[40,2555,2556,2558,2560,2562],{"class":42,"line":163},[40,2557,2507],{"class":56},[40,2559,2476],{"class":53},[40,2561,1706],{"class":56},[40,2563,2564],{"class":46},"\u002F\u002F \"Alice\" (arrow has lexical this)\n",[40,2566,2567],{"class":42,"line":197},[40,2568,91],{"emptyLinePlaceholder":90},[40,2570,2571],{"class":42,"line":211},[40,2572,2573],{"class":46},"\u002F\u002F ── setInterval doesn't wait for the callback to finish ──\n",[40,2575,2576,2578,2580,2582,2584,2586],{"class":42,"line":217},[40,2577,25],{"class":53},[40,2579,70],{"class":56},[40,2581,2045],{"class":60},[40,2583,1776],{"class":56},[40,2585,61],{"class":60},[40,2587,264],{"class":56},[40,2589,2590,2593,2596,2598],{"class":42,"line":223},[40,2591,2592],{"class":60},"    await",[40,2594,2595],{"class":53}," longRunningTask",[40,2597,1706],{"class":56},[40,2599,2600],{"class":46},"\u002F\u002F takes 3s, interval is 1s\n",[40,2602,2603],{"class":42,"line":228},[40,2604,2605],{"class":46},"    \u002F\u002F ⚠️ intervals pile up! After 3s, 3 pending callbacks queue up.\n",[40,2607,2608],{"class":42,"line":234},[40,2609,2610],{"class":46},"    \u002F\u002F Fix: use self-scheduling setTimeout (recursive) instead of setInterval:\n",[40,2612,2613,2615,2617],{"class":42,"line":255},[40,2614,340],{"class":56},[40,2616,81],{"class":80},[40,2618,84],{"class":56},[40,2620,2621],{"class":42,"line":267},[40,2622,91],{"emptyLinePlaceholder":90},[40,2624,2625],{"class":42,"line":292},[40,2626,2627],{"class":46},"\u002F\u002F ── Timer callbacks are macrotasks (run after all microtasks) ──\n",[40,2629,2630,2632,2634,2636,2638,2640,2642,2645,2647,2649],{"class":42,"line":313},[40,2631,21],{"class":53},[40,2633,57],{"class":56},[40,2635,61],{"class":60},[40,2637,64],{"class":56},[40,2639,67],{"class":53},[40,2641,70],{"class":56},[40,2643,2644],{"class":73},"\"timer\"",[40,2646,77],{"class":56},[40,2648,143],{"class":80},[40,2650,84],{"class":56},[40,2652,2653,2655,2657,2659,2661,2663,2665,2667,2669,2671,2673,2676],{"class":42,"line":331},[40,2654,166],{"class":80},[40,2656,169],{"class":56},[40,2658,172],{"class":53},[40,2660,175],{"class":56},[40,2662,178],{"class":53},[40,2664,57],{"class":56},[40,2666,61],{"class":60},[40,2668,64],{"class":56},[40,2670,67],{"class":53},[40,2672,70],{"class":56},[40,2674,2675],{"class":73},"\"promise\"",[40,2677,194],{"class":56},[40,2679,2680],{"class":42,"line":337},[40,2681,2682],{"class":46},"\u002F\u002F Output: promise, timer (microtask runs first)\n",[40,2684,2685],{"class":42,"line":347},[40,2686,91],{"emptyLinePlaceholder":90},[40,2688,2689],{"class":42,"line":353},[40,2690,2691],{"class":46},"\u002F\u002F ── Timers don't fire when the tab is hidden (throttled to 1\u002Fsec) ──\n",[40,2693,2694],{"class":42,"line":359},[40,2695,2696],{"class":46},"\u002F\u002F In background tabs, setTimeout\u002FsetInterval are throttled to ~1\u002Fsec (battery saving).\n",[40,2698,2699],{"class":42,"line":365},[40,2700,2701],{"class":46},"\u002F\u002F Use Web Workers for timers that must run at full speed in background.\n",[40,2703,2704],{"class":42,"line":371},[40,2705,91],{"emptyLinePlaceholder":90},[40,2707,2708],{"class":42,"line":376},[40,2709,2710],{"class":46},"\u002F\u002F ── clearInterval doesn't cancel a callback that's already executing ──\n",[40,2712,2713],{"class":42,"line":382},[40,2714,2715],{"class":46},"\u002F\u002F If the callback is running when you call clearInterval, it finishes.\n",[40,2717,2718],{"class":42,"line":406},[40,2719,2720],{"class":46},"\u002F\u002F clearInterval only prevents FUTURE invocations.\n",[15,2722,2724],{"id":2723},"quick-quiz","🧠 Quick Quiz",[2726,2727,2728,2729,2731],"p",{},"Why does the ",[19,2730,25],{}," callback not fire exactly every 1000ms?",[28,2733,2734],{"language":30},[32,2735,2737],{"className":34,"code":2736,"language":30,"meta":36,"style":36},"setInterval(() => {\n    const start = Date.now();\n    while (Date.now() - start \u003C 300) {}  \u002F\u002F block for 300ms\n    console.log(\"tick\");\n}, 1000);\n",[19,2738,2739,2749,2764,2790,2803],{"__ignoreMap":36},[40,2740,2741,2743,2745,2747],{"class":42,"line":43},[40,2742,25],{"class":53},[40,2744,57],{"class":56},[40,2746,61],{"class":60},[40,2748,264],{"class":56},[40,2750,2751,2753,2756,2758,2760,2762],{"class":42,"line":50},[40,2752,270],{"class":60},[40,2754,2755],{"class":80}," start",[40,2757,276],{"class":60},[40,2759,246],{"class":56},[40,2761,249],{"class":53},[40,2763,252],{"class":56},[40,2765,2766,2768,2771,2773,2775,2777,2779,2781,2784,2787],{"class":42,"line":87},[40,2767,852],{"class":60},[40,2769,2770],{"class":56}," (Date.",[40,2772,249],{"class":53},[40,2774,283],{"class":56},[40,2776,286],{"class":60},[40,2778,575],{"class":56},[40,2780,672],{"class":60},[40,2782,2783],{"class":80}," 300",[40,2785,2786],{"class":56},") {}  ",[40,2788,2789],{"class":46},"\u002F\u002F block for 300ms\n",[40,2791,2792,2794,2796,2798,2801],{"class":42,"line":94},[40,2793,295],{"class":56},[40,2795,67],{"class":53},[40,2797,70],{"class":56},[40,2799,2800],{"class":73},"\"tick\"",[40,2802,84],{"class":56},[40,2804,2805,2807,2809],{"class":42,"line":100},[40,2806,340],{"class":56},[40,2808,81],{"class":80},[40,2810,84],{"class":56},[2812,2813,2814,2818,2828,2831,2853,2860,2872],"details",{},[2815,2816,2817],"summary",{},"Answer",[2726,2819,2820,2822,2823,2827],{},[19,2821,25],{}," targets the ",[2824,2825,2826],"strong",{},"original schedule",", not the previous execution end time. The interval fires at 1000ms, 2000ms, 3000ms, etc. — regardless of how long each callback takes.",[2726,2829,2830],{},"However, since each callback blocks the main thread for 300ms, and the timer is a macrotask, the actual firing time depends on when the event loop can process the timer:",[2832,2833,2834,2841,2847],"ul",{},[2835,2836,2837,2840],"li",{},[2824,2838,2839],{},"Tick 1",": fires at 1000ms. Callback blocks until 1300ms.",[2835,2842,2843,2846],{},[2824,2844,2845],{},"Tick 2",": scheduled for 2000ms. The event loop is free at 1300ms. The timer fires at 2000ms (on schedule). Callback blocks until 2300ms.",[2835,2848,2849,2852],{},[2824,2850,2851],{},"Tick 3",": scheduled for 3000ms. Event loop free at 2300ms. Fires at 3000ms.",[2726,2854,2855,2856,2859],{},"So the intervals fire roughly on schedule (1000ms apart), but if the callback takes ",[2824,2857,2858],{},"longer than the interval"," (e.g., 1200ms with a 1000ms interval), the next tick fires immediately after the previous callback ends (they \"stack up\").",[2726,2861,2862,2865,2866,2868,2869,2871],{},[2824,2863,2864],{},"The lesson",": ",[19,2867,25],{}," doesn't wait for the previous callback to finish — it targets fixed intervals. If callbacks take longer than the interval, they pile up. Use recursive ",[19,2870,21],{}," instead if you need guaranteed gaps:",[32,2873,2875],{"className":34,"code":2874,"language":30,"meta":36,"style":36},"function tick() {\n    const start = Date.now();\n    while (Date.now() - start \u003C 300) {}\n    console.log(\"tick\");\n    setTimeout(tick, 1000);  \u002F\u002F schedule next AFTER this one finishes (guaranteed 1000ms gap)\n}\nsetTimeout(tick, 1000);\n",[19,2876,2877,2885,2899,2920,2932,2946,2950],{"__ignoreMap":36},[40,2878,2879,2881,2883],{"class":42,"line":43},[40,2880,385],{"class":60},[40,2882,434],{"class":53},[40,2884,437],{"class":56},[40,2886,2887,2889,2891,2893,2895,2897],{"class":42,"line":50},[40,2888,270],{"class":60},[40,2890,2755],{"class":80},[40,2892,276],{"class":60},[40,2894,246],{"class":56},[40,2896,249],{"class":53},[40,2898,252],{"class":56},[40,2900,2901,2903,2905,2907,2909,2911,2913,2915,2917],{"class":42,"line":87},[40,2902,852],{"class":60},[40,2904,2770],{"class":56},[40,2906,249],{"class":53},[40,2908,283],{"class":56},[40,2910,286],{"class":60},[40,2912,575],{"class":56},[40,2914,672],{"class":60},[40,2916,2783],{"class":80},[40,2918,2919],{"class":56},") {}\n",[40,2921,2922,2924,2926,2928,2930],{"class":42,"line":94},[40,2923,295],{"class":56},[40,2925,67],{"class":53},[40,2927,70],{"class":56},[40,2929,2800],{"class":73},[40,2931,84],{"class":56},[40,2933,2934,2936,2939,2941,2943],{"class":42,"line":100},[40,2935,526],{"class":53},[40,2937,2938],{"class":56},"(tick, ",[40,2940,81],{"class":80},[40,2942,645],{"class":56},[40,2944,2945],{"class":46},"\u002F\u002F schedule next AFTER this one finishes (guaranteed 1000ms gap)\n",[40,2947,2948],{"class":42,"line":106},[40,2949,535],{"class":56},[40,2951,2952,2954,2956,2958],{"class":42,"line":112},[40,2953,21],{"class":53},[40,2955,2938],{"class":56},[40,2957,81],{"class":80},[40,2959,84],{"class":56},[2961,2962,2963],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":36,"searchDepth":50,"depth":50,"links":2965},[2966,2968,2970,2971,2972,2973,2974],{"id":17,"depth":50,"text":2967},"setTimeout \u002F setInterval: Accuracy and Drift",{"id":538,"depth":50,"text":2969},"requestAnimationFrame: Sync with Display Refresh",{"id":946,"depth":50,"text":947},{"id":1665,"depth":50,"text":1666},{"id":1967,"depth":50,"text":1968},{"id":2419,"depth":50,"text":2420},{"id":2723,"depth":50,"text":2724},"Deep-dive into JavaScript timers: setTimeout\u002FsetInterval drift and accuracy, requestAnimationFrame for smooth animation, debounce and throttle implementation, and AbortController for timer cleanup. Code-first reference for senior engineers.","md",{},"\u002Fjs\u002F22-timers-and-scheduling",{"title":5,"description":2975},"js\u002F22-timers-and-scheduling","kpvWsaNR6bNwdhWsSvgwmuMOBI1Nq0wk7jz58QKOhdE",1789924649500]