[{"data":1,"prerenderedAt":1379},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Fdebouncing-vs-yielding-for-input-events":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Fdebouncing-vs-yielding-for-input-events":1371},{"id":4,"title":5,"body":6,"description":1350,"extension":1351,"meta":1352,"navigation":1364,"path":1365,"seo":1366,"stem":1369,"__hash__":1370},"content\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Fdebouncing-vs-yielding-for-input-events\u002Findex.md","Debouncing vs Yielding for Input Events",{"type":7,"value":8,"toc":1334},"minimark",[9,14,39,42,204,209,242,246,339,343,349,355,361,367,499,503,508,717,720,724,951,957,961,964,1085,1088,1092,1100,1230,1234,1240,1244,1251,1255,1258,1262,1274,1283,1292,1296,1319,1324,1327,1330],[10,11,13],"h1",{"id":12},"debouncing-vs-yielding-for-input-events-which-one-fixes-inp","Debouncing vs Yielding for Input Events: Which One Fixes INP?",[15,16,17,18,23,24,28,29,33,34,38],"p",{},"This comparison sits under ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002F","Profiling Event Handlers for INP"," in ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement",". Both techniques are routinely recommended for \"slow input handling\", and they solve different problems. Debouncing reduces ",[30,31,32],"em",{},"how often"," expensive work runs by waiting until input pauses. Yielding — with ",[35,36,37],"code",{},"scheduler.yield()"," or an equivalent — keeps the work but splits it into chunks so the browser can process input and paint between them.",[15,40,41],{},"Choosing wrongly is common. A team debounces a 200ms filter by 300ms and is surprised INP does not improve: the filter still runs as one 200ms task, and any keystroke that arrives during it waits. Another team yields inside a filter that runs on every keystroke and is surprised the page feels busy: the work is interruptible, but it is still done twenty times per word.",[15,43,44],{},[45,46,52,53,52,60,52,64,52,67,52,76,52,82,52,87,52,97,52,104,52,107,52,110,52,113,52,116,52,120,52,124,52,129,52,133,52,138,52,141,52,144,52,147,52,150,52,157,52,163,52,166,52,170,52,173,52,177,52,180,52,184,52,187,52,191,52,195,52,200,52],"svg",{"viewBox":47,"width":48,"role":49,"ariaLabel":50,"style":51},"0 0 760 260","100%","img","Timelines of three keystrokes handled with no mitigation, with debouncing, and with yielding, showing where input waits behind long tasks.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[54,55],"rect",{"className":56,"x":58,"y":58,"width":48,"height":48,"fill":59},[57],"svg-canvas","0","#ffffff",[61,62,63],"title",{},"Three keystrokes under three strategies",[65,66,50],"desc",{},[54,68],{"x":69,"y":69,"width":70,"height":71,"rx":72,"fill":73,"stroke":74,"style":75},"1","758","258","10","none","currentColor","stroke-opacity:0.18",[77,78,63],"text",{"x":79,"y":80,"fill":74,"style":81},"28.0","34.0","font-size:16px;font-weight:700",[77,83,86],{"x":79,"y":84,"fill":74,"style":85},"74.0","font-size:12.5px;font-weight:700","None",[54,88],{"x":89,"y":90,"width":91,"height":92,"rx":93,"fill":94,"stroke":95,"style":96},"108.0","56.0","136.0","26.0","3","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[77,98,103],{"x":99,"y":100,"fill":74,"style":101,"textAnchor":102},"176.0","73.5","font-size:11.5px","middle","filter",[54,105],{"x":106,"y":90,"width":91,"height":92,"rx":93,"fill":94,"stroke":95,"style":96},"245.5",[77,108,103],{"x":109,"y":100,"fill":74,"style":101,"textAnchor":102},"313.5",[54,111],{"x":112,"y":90,"width":91,"height":92,"rx":93,"fill":94,"stroke":95,"style":96},"383.0",[77,114,103],{"x":115,"y":100,"fill":74,"style":101,"textAnchor":102},"451.0",[77,117,119],{"x":79,"y":118,"fill":74,"style":85},"112.0","Debounce",[54,121],{"x":122,"y":123,"width":91,"height":92,"rx":93,"fill":94,"stroke":95,"style":96},"520.5","94.0",[77,125,128],{"x":126,"y":127,"fill":74,"style":101,"textAnchor":102},"588.5","111.5","one filter (200ms)",[77,130,132],{"x":79,"y":131,"fill":74,"style":85},"150.0","Yield",[54,134],{"x":89,"y":135,"width":92,"height":92,"rx":93,"fill":136,"stroke":136,"style":137},"132.0","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[54,139],{"x":140,"y":135,"width":92,"height":92,"rx":93,"fill":136,"stroke":136,"style":137},"138.9",[54,142],{"x":143,"y":135,"width":92,"height":92,"rx":93,"fill":136,"stroke":136,"style":137},"169.8",[54,145],{"x":146,"y":135,"width":92,"height":92,"rx":93,"fill":136,"stroke":136,"style":137},"200.8",[54,148],{"x":149,"y":135,"width":92,"height":92,"rx":93,"fill":136,"stroke":136,"style":137},"231.7",[151,152],"line",{"x1":153,"y1":154,"x2":155,"y2":154,"stroke":74,"style":156},"107.2","172.0","726.0","stroke-opacity:0.4",[77,158,162],{"x":159,"y":160,"fill":161,"style":101,"textAnchor":102},"119.2","189.0","#51617a","0ms",[77,164,165],{"x":99,"y":160,"fill":161,"style":101,"textAnchor":102},"100ms",[77,167,169],{"x":168,"y":160,"fill":161,"style":101,"textAnchor":102},"244.7","200ms",[77,171,172],{"x":109,"y":160,"fill":161,"style":101,"textAnchor":102},"300ms",[77,174,176],{"x":175,"y":160,"fill":161,"style":101,"textAnchor":102},"382.2","400ms",[77,178,179],{"x":115,"y":160,"fill":161,"style":101,"textAnchor":102},"500ms",[77,181,183],{"x":182,"y":160,"fill":161,"style":101,"textAnchor":102},"519.7","600ms",[77,185,186],{"x":126,"y":160,"fill":161,"style":101,"textAnchor":102},"700ms",[77,188,190],{"x":189,"y":160,"fill":161,"style":101,"textAnchor":102},"657.2","800ms",[77,192,194],{"x":193,"y":160,"fill":161,"style":101,"textAnchor":102},"713.3","900ms",[77,196,199],{"x":79,"y":197,"fill":161,"style":198},"220.0","font-size:12.5px","Debouncing runs the work once but still as a 200ms block; yielding keeps every chunk under 50ms but runs the work",[77,201,203],{"x":79,"y":202,"fill":161,"style":198},"238.0","for each keystroke unless combined with cancellation.",[205,206,208],"h2",{"id":207},"rapid-diagnosis-which-problem-do-you-have","Rapid Diagnosis: Which Problem Do You Have?",[210,211,212,220,230,236],"ul",{},[213,214,215,219],"li",{},[216,217,218],"strong",{},"Is the work redundant?"," If only the result for the latest input matters (search filtering, validation, autosave), most runs are wasted — debounce or cancel.",[213,221,222,225,226,229],{},[216,223,224],{},"Is each run long?"," If one run exceeds ",[35,227,228],{},"50ms"," on a throttled device, it can block an interaction whenever it happens — yield or move it off-thread.",[213,231,232,235],{},[216,233,234],{},"Does input arrive during the work?"," In a trace of fast typing, if keystroke interactions show large input delay overlapping your work, the work is blocking input regardless of frequency.",[213,237,238,241],{},[216,239,240],{},"Do users need intermediate results?"," Live previews (a drawing app, a slider) need updates during input — throttle and keep each update cheap; do not debounce.",[205,243,245],{"id":244},"the-comparison","The Comparison",[247,248,249,267],"table",{},[250,251,252],"thead",{},[253,254,255,259,261,264],"tr",{},[256,257,258],"th",{},"Axis",[256,260,119],{},[256,262,263],{},"Throttle",[256,265,266],{},"Yield inside work",[268,269,270,285,297,311,325],"tbody",{},[253,271,272,276,279,282],{},[273,274,275],"td",{},"Reduces total work",[273,277,278],{},"Yes, drastically",[273,280,281],{},"Partly",[273,283,284],{},"No",[253,286,287,290,292,294],{},[273,288,289],{},"Bounds longest task",[273,291,284],{},[273,293,284],{},[273,295,296],{},"Yes (\u003C 50ms chunks)",[253,298,299,302,305,308],{},[273,300,301],{},"Effect on INP",[273,303,304],{},"Only if runs no longer overlap input",[273,306,307],{},"Limited",[273,309,310],{},"Direct — input can interleave",[253,312,313,316,319,322],{},[273,314,315],{},"Result latency",[273,317,318],{},"Waits for pause + work time",[273,320,321],{},"Regular updates",[273,323,324],{},"Work time, slightly longer",[253,326,327,330,333,336],{},[273,328,329],{},"Best for",[273,331,332],{},"Search, validation, autosave",[273,334,335],{},"Scroll\u002Fresize-driven UI, previews",[273,337,338],{},"Unavoidable long jobs",[205,340,342],{"id":341},"root-cause-analysis-why-each-fails-alone","Root Cause Analysis: Why Each Fails Alone",[15,344,345,348],{},[216,346,347],{},"1. Debounce does not shorten tasks."," When the debounced callback fires, it runs at full length. A user who resumes typing just as it fires waits for all of it — input delay equals the remaining task time.",[15,350,351,354],{},[216,352,353],{},"2. Yield does not reduce work."," Splitting 200ms into five 40ms chunks keeps the thread responsive, but running it per keystroke means the CPU is constantly busy, battery drains and results for stale queries are computed for nothing.",[15,356,357,360],{},[216,358,359],{},"3. Throttle sits awkwardly between them."," It caps frequency without waiting for a pause, which suits continuous inputs like scroll but still runs long tasks at full length.",[15,362,363,366],{},[216,364,365],{},"4. Stale work is not cancelled."," Neither technique on its own stops a run that is already obsolete because a newer input arrived.",[15,368,369],{},[45,370,52,373,52,376,52,379,52,381,52,398,52,401,52,403,52,409,52,415,52,418,52,422,52,429,52,435,52,439,52,444,52,447,52,451,52,453,52,456,52,459,52,462,52,465,52,468,52,471,52,474,52,476,52,479,52,482,52,485,52,489,52,492,52,495,52],{"viewBox":371,"width":48,"role":49,"ariaLabel":372,"style":51},"0 0 760 325","Decision sequence choosing between debouncing, throttling, yielding and offloading based on whether results must be live and how long each run is.",[54,374],{"className":375,"x":58,"y":58,"width":48,"height":48,"fill":59},[57],[61,377,378],{},"Picking the right technique",[65,380,372],{},[382,383,384],"defs",{},[385,386,393],"marker",{"id":387,"viewBox":388,"refX":389,"refY":390,"markerWidth":391,"markerHeight":391,"orient":392},"fa6f3af13e","0 0 10 10","9","5","7","auto-start-reverse",[394,395],"path",{"d":396,"fill":74,"style":397},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[54,399],{"x":69,"y":69,"width":70,"height":400,"rx":72,"fill":73,"stroke":74,"style":75},"323",[77,402,378],{"x":79,"y":80,"fill":74,"style":81},[54,404],{"x":79,"y":90,"width":405,"height":406,"rx":407,"fill":74,"stroke":74,"style":408},"320.0","35.0","6","fill-opacity:0.06;stroke-opacity:0.4",[77,410,414],{"x":411,"y":412,"fill":74,"style":413,"textAnchor":102},"188.0","78.0","font-size:13px;font-weight:700","Only the latest input's result matters?",[54,416],{"x":417,"y":90,"width":405,"height":406,"rx":407,"fill":136,"stroke":136,"style":137},"412.0",[77,419,421],{"x":420,"y":412,"fill":74,"style":198,"textAnchor":102},"572.0","Debounce (150-300ms) and cancel stale runs",[151,423],{"x1":424,"y1":100,"x2":425,"y2":100,"stroke":74,"strokeWidth":426,"style":427,"markerEnd":428},"348.0","410.0","1.5","stroke-opacity:0.6","url(#fa6f3af13e)",[77,430,434],{"x":431,"y":432,"fill":136,"style":433,"textAnchor":102},"380.0","66.5","font-size:11.5px;font-weight:700","yes",[151,436],{"x1":411,"y1":437,"x2":411,"y2":438,"stroke":74,"strokeWidth":426,"style":427,"markerEnd":428},"91.0","125.0",[77,440,443],{"x":441,"y":442,"fill":161,"style":433},"196.0","113.0","no",[54,445],{"x":79,"y":446,"width":405,"height":406,"rx":407,"fill":74,"stroke":74,"style":408},"127.0",[77,448,450],{"x":411,"y":449,"fill":74,"style":413,"textAnchor":102},"149.0","Need live updates during input?",[54,452],{"x":417,"y":446,"width":405,"height":406,"rx":407,"fill":136,"stroke":136,"style":137},[77,454,455],{"x":420,"y":449,"fill":74,"style":198,"textAnchor":102},"Throttle to frame rate and keep each update tiny",[151,457],{"x1":424,"y1":458,"x2":425,"y2":458,"stroke":74,"strokeWidth":426,"style":427,"markerEnd":428},"144.5",[77,460,434],{"x":431,"y":461,"fill":136,"style":433,"textAnchor":102},"137.5",[151,463],{"x1":411,"y1":464,"x2":411,"y2":441,"stroke":74,"strokeWidth":426,"style":427,"markerEnd":428},"162.0",[77,466,443],{"x":441,"y":467,"fill":161,"style":433},"184.0",[54,469],{"x":79,"y":470,"width":405,"height":406,"rx":407,"fill":74,"stroke":74,"style":408},"198.0",[77,472,473],{"x":411,"y":197,"fill":74,"style":413,"textAnchor":102},"Each run still over 50ms?",[54,475],{"x":417,"y":470,"width":405,"height":406,"rx":407,"fill":136,"stroke":136,"style":137},[77,477,478],{"x":420,"y":197,"fill":74,"style":198,"textAnchor":102},"Also yield inside the run, or use a worker",[151,480],{"x1":424,"y1":481,"x2":425,"y2":481,"stroke":74,"strokeWidth":426,"style":427,"markerEnd":428},"215.5",[77,483,434],{"x":431,"y":484,"fill":136,"style":433,"textAnchor":102},"208.5",[151,486],{"x1":411,"y1":487,"x2":411,"y2":488,"stroke":74,"strokeWidth":426,"style":427,"markerEnd":428},"233.0","267.0",[77,490,443],{"x":441,"y":491,"fill":161,"style":433},"255.0",[54,493],{"x":79,"y":494,"width":405,"height":80,"rx":407,"fill":94,"stroke":95,"style":96},"269.0",[77,496,498],{"x":411,"y":497,"fill":74,"style":198,"textAnchor":102},"290.5","Leave it — the work is cheap enough to run directly",[205,500,502],{"id":501},"step-by-step-combining-them-correctly","Step-by-Step: Combining Them Correctly",[504,505,507],"h3",{"id":506},"_1-debounce-redundant-work-with-cancellation","1. Debounce redundant work, with cancellation",[509,510,515],"pre",{"className":511,"code":512,"language":513,"meta":514,"style":514},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","function debounceLatest(fn, wait) {\n  let timer, controller;\n  return (...args) => {\n    clearTimeout(timer);\n    controller?.abort();                         \u002F\u002F cancel a run already in progress\n    timer = setTimeout(() => { controller = new AbortController(); fn(controller.signal, ...args); }, wait);\n  };\n}\nconst onInput = debounceLatest((signal, q) => filterResults(q, signal), 200);\n\u002F\u002F trade-off: a 200ms wait adds 200ms before results appear after the last\n\u002F\u002F keystroke. For a typeahead that feels slow; use 100-150ms there, and show\n\u002F\u002F cached results for prefixes immediately.\n","javascript","",[35,516,517,546,555,579,588,604,645,651,657,699,705,711],{"__ignoreMap":514},[518,519,521,525,529,533,537,540,543],"span",{"class":151,"line":520},1,[518,522,524],{"class":523},"sPARh","function",[518,526,528],{"class":527},"smZ65"," debounceLatest",[518,530,532],{"class":531},"saISM","(",[518,534,536],{"class":535},"sQw3B","fn",[518,538,539],{"class":531},", ",[518,541,542],{"class":535},"wait",[518,544,545],{"class":531},") {\n",[518,547,549,552],{"class":151,"line":548},2,[518,550,551],{"class":523},"  let",[518,553,554],{"class":531}," timer, controller;\n",[518,556,558,561,564,567,570,573,576],{"class":151,"line":557},3,[518,559,560],{"class":523},"  return",[518,562,563],{"class":531}," (",[518,565,566],{"class":523},"...",[518,568,569],{"class":535},"args",[518,571,572],{"class":531},") ",[518,574,575],{"class":523},"=>",[518,577,578],{"class":531}," {\n",[518,580,582,585],{"class":151,"line":581},4,[518,583,584],{"class":527},"    clearTimeout",[518,586,587],{"class":531},"(timer);\n",[518,589,591,594,597,600],{"class":151,"line":590},5,[518,592,593],{"class":531},"    controller?.",[518,595,596],{"class":527},"abort",[518,598,599],{"class":531},"();                         ",[518,601,603],{"class":602},"sjfSM","\u002F\u002F cancel a run already in progress\n",[518,605,607,610,613,616,619,621,624,626,629,632,635,637,640,642],{"class":151,"line":606},6,[518,608,609],{"class":531},"    timer ",[518,611,612],{"class":523},"=",[518,614,615],{"class":527}," setTimeout",[518,617,618],{"class":531},"(() ",[518,620,575],{"class":523},[518,622,623],{"class":531}," { controller ",[518,625,612],{"class":523},[518,627,628],{"class":523}," new",[518,630,631],{"class":527}," AbortController",[518,633,634],{"class":531},"(); ",[518,636,536],{"class":527},[518,638,639],{"class":531},"(controller.signal, ",[518,641,566],{"class":523},[518,643,644],{"class":531},"args); }, wait);\n",[518,646,648],{"class":151,"line":647},7,[518,649,650],{"class":531},"  };\n",[518,652,654],{"class":151,"line":653},8,[518,655,656],{"class":531},"}\n",[518,658,660,663,667,670,672,675,678,680,683,685,687,690,693,696],{"class":151,"line":659},9,[518,661,662],{"class":523},"const",[518,664,666],{"class":665},"sPXB4"," onInput",[518,668,669],{"class":523}," =",[518,671,528],{"class":527},[518,673,674],{"class":531},"((",[518,676,677],{"class":535},"signal",[518,679,539],{"class":531},[518,681,682],{"class":535},"q",[518,684,572],{"class":531},[518,686,575],{"class":523},[518,688,689],{"class":527}," filterResults",[518,691,692],{"class":531},"(q, signal), ",[518,694,695],{"class":665},"200",[518,697,698],{"class":531},");\n",[518,700,702],{"class":151,"line":701},10,[518,703,704],{"class":602},"\u002F\u002F trade-off: a 200ms wait adds 200ms before results appear after the last\n",[518,706,708],{"class":151,"line":707},11,[518,709,710],{"class":602},"\u002F\u002F keystroke. For a typeahead that feels slow; use 100-150ms there, and show\n",[518,712,714],{"class":151,"line":713},12,[518,715,716],{"class":602},"\u002F\u002F cached results for prefixes immediately.\n",[15,718,719],{},"Expected outcome: the expensive work runs once per pause instead of once per keystroke.",[504,721,723],{"id":722},"_2-yield-inside-the-work-checking-for-cancellation","2. Yield inside the work, checking for cancellation",[509,725,727],{"className":511,"code":726,"language":513,"meta":514,"style":514},"async function filterResults(query, signal) {\n  const out = [];\n  let deadline = performance.now() + 40;\n  for (const item of items) {\n    if (matches(item, query)) out.push(item);\n    if (performance.now() > deadline) {\n      await (globalThis.scheduler?.yield?.() ?? new Promise((r) => setTimeout(r)));\n      if (signal.aborted) return;                \u002F\u002F newer input arrived — stop\n      deadline = performance.now() + 40;\n    }\n  }\n  render(out);\n}\n\u002F\u002F trade-off: checking `signal` only at yield points means up to 40ms of stale\n\u002F\u002F work still runs. That is the price of not checking on every iteration.\n",[35,728,729,750,763,790,808,827,844,880,897,916,921,926,934,939,945],{"__ignoreMap":514},[518,730,731,734,737,739,741,744,746,748],{"class":151,"line":520},[518,732,733],{"class":523},"async",[518,735,736],{"class":523}," function",[518,738,689],{"class":527},[518,740,532],{"class":531},[518,742,743],{"class":535},"query",[518,745,539],{"class":531},[518,747,677],{"class":535},[518,749,545],{"class":531},[518,751,752,755,758,760],{"class":151,"line":548},[518,753,754],{"class":523},"  const",[518,756,757],{"class":665}," out",[518,759,669],{"class":523},[518,761,762],{"class":531}," [];\n",[518,764,765,767,770,772,775,778,781,784,787],{"class":151,"line":557},[518,766,551],{"class":523},[518,768,769],{"class":531}," deadline ",[518,771,612],{"class":523},[518,773,774],{"class":531}," performance.",[518,776,777],{"class":527},"now",[518,779,780],{"class":531},"() ",[518,782,783],{"class":523},"+",[518,785,786],{"class":665}," 40",[518,788,789],{"class":531},";\n",[518,791,792,795,797,799,802,805],{"class":151,"line":581},[518,793,794],{"class":523},"  for",[518,796,563],{"class":531},[518,798,662],{"class":523},[518,800,801],{"class":665}," item",[518,803,804],{"class":523}," of",[518,806,807],{"class":531}," items) {\n",[518,809,810,813,815,818,821,824],{"class":151,"line":590},[518,811,812],{"class":523},"    if",[518,814,563],{"class":531},[518,816,817],{"class":527},"matches",[518,819,820],{"class":531},"(item, query)) out.",[518,822,823],{"class":527},"push",[518,825,826],{"class":531},"(item);\n",[518,828,829,831,834,836,838,841],{"class":151,"line":606},[518,830,812],{"class":523},[518,832,833],{"class":531}," (performance.",[518,835,777],{"class":527},[518,837,780],{"class":531},[518,839,840],{"class":523},">",[518,842,843],{"class":531}," deadline) {\n",[518,845,846,849,852,855,858,861,863,866,868,871,873,875,877],{"class":151,"line":647},[518,847,848],{"class":523},"      await",[518,850,851],{"class":531}," (globalThis.scheduler?.",[518,853,854],{"class":527},"yield",[518,856,857],{"class":531},"?.() ",[518,859,860],{"class":523},"??",[518,862,628],{"class":523},[518,864,865],{"class":665}," Promise",[518,867,674],{"class":531},[518,869,870],{"class":535},"r",[518,872,572],{"class":531},[518,874,575],{"class":523},[518,876,615],{"class":527},[518,878,879],{"class":531},"(r)));\n",[518,881,882,885,888,891,894],{"class":151,"line":653},[518,883,884],{"class":523},"      if",[518,886,887],{"class":531}," (signal.aborted) ",[518,889,890],{"class":523},"return",[518,892,893],{"class":531},";                ",[518,895,896],{"class":602},"\u002F\u002F newer input arrived — stop\n",[518,898,899,902,904,906,908,910,912,914],{"class":151,"line":659},[518,900,901],{"class":531},"      deadline ",[518,903,612],{"class":523},[518,905,774],{"class":531},[518,907,777],{"class":527},[518,909,780],{"class":531},[518,911,783],{"class":523},[518,913,786],{"class":665},[518,915,789],{"class":531},[518,917,918],{"class":151,"line":701},[518,919,920],{"class":531},"    }\n",[518,922,923],{"class":151,"line":707},[518,924,925],{"class":531},"  }\n",[518,927,928,931],{"class":151,"line":713},[518,929,930],{"class":527},"  render",[518,932,933],{"class":531},"(out);\n",[518,935,937],{"class":151,"line":936},13,[518,938,656],{"class":531},[518,940,942],{"class":151,"line":941},14,[518,943,944],{"class":602},"\u002F\u002F trade-off: checking `signal` only at yield points means up to 40ms of stale\n",[518,946,948],{"class":151,"line":947},15,[518,949,950],{"class":602},"\u002F\u002F work still runs. That is the price of not checking on every iteration.\n",[15,952,953,954,956],{},"Expected outcome: no task over ",[35,955,228],{},", and stale runs stop at the next yield point rather than running to completion.",[504,958,960],{"id":959},"_3-throttle-continuous-inputs-to-the-frame-rate","3. Throttle continuous inputs to the frame rate",[15,962,963],{},"For sliders, drag handles and scroll-linked UI, update at most once per frame and keep each update cheap.",[509,965,967],{"className":511,"code":966,"language":513,"meta":514,"style":514},"let queued = false, latest;\nslider.addEventListener('input', (e) => {\n  latest = e.target.value;\n  if (queued) return;\n  queued = true;\n  requestAnimationFrame(() => { queued = false; preview(latest); });\n});\n\u002F\u002F trade-off: rAF throttling ties updates to display refresh — 120Hz screens get\n\u002F\u002F twice as many updates. If preview() is heavy, throttle to a fixed interval.\n",[35,968,969,985,1011,1021,1033,1045,1070,1075,1080],{"__ignoreMap":514},[518,970,971,974,977,979,982],{"class":151,"line":520},[518,972,973],{"class":523},"let",[518,975,976],{"class":531}," queued ",[518,978,612],{"class":523},[518,980,981],{"class":665}," false",[518,983,984],{"class":531},", latest;\n",[518,986,987,990,993,995,999,1002,1005,1007,1009],{"class":151,"line":548},[518,988,989],{"class":531},"slider.",[518,991,992],{"class":527},"addEventListener",[518,994,532],{"class":531},[518,996,998],{"class":997},"sZ8jY","'input'",[518,1000,1001],{"class":531},", (",[518,1003,1004],{"class":535},"e",[518,1006,572],{"class":531},[518,1008,575],{"class":523},[518,1010,578],{"class":531},[518,1012,1013,1016,1018],{"class":151,"line":557},[518,1014,1015],{"class":531},"  latest ",[518,1017,612],{"class":523},[518,1019,1020],{"class":531}," e.target.value;\n",[518,1022,1023,1026,1029,1031],{"class":151,"line":581},[518,1024,1025],{"class":523},"  if",[518,1027,1028],{"class":531}," (queued) ",[518,1030,890],{"class":523},[518,1032,789],{"class":531},[518,1034,1035,1038,1040,1043],{"class":151,"line":590},[518,1036,1037],{"class":531},"  queued ",[518,1039,612],{"class":523},[518,1041,1042],{"class":665}," true",[518,1044,789],{"class":531},[518,1046,1047,1050,1052,1054,1057,1059,1061,1064,1067],{"class":151,"line":606},[518,1048,1049],{"class":527},"  requestAnimationFrame",[518,1051,618],{"class":531},[518,1053,575],{"class":523},[518,1055,1056],{"class":531}," { queued ",[518,1058,612],{"class":523},[518,1060,981],{"class":665},[518,1062,1063],{"class":531},"; ",[518,1065,1066],{"class":527},"preview",[518,1068,1069],{"class":531},"(latest); });\n",[518,1071,1072],{"class":151,"line":647},[518,1073,1074],{"class":531},"});\n",[518,1076,1077],{"class":151,"line":653},[518,1078,1079],{"class":602},"\u002F\u002F trade-off: rAF throttling ties updates to display refresh — 120Hz screens get\n",[518,1081,1082],{"class":151,"line":659},[518,1083,1084],{"class":602},"\u002F\u002F twice as many updates. If preview() is heavy, throttle to a fixed interval.\n",[15,1086,1087],{},"Expected outcome: one preview per frame, with input events themselves doing almost nothing.",[504,1089,1091],{"id":1090},"_4-move-genuinely-heavy-work-to-a-worker","4. Move genuinely heavy work to a worker",[15,1093,1094,1095,1099],{},"If even chunked work is too much CPU for the device, ",[19,1096,1098],{"href":1097},"\u002Fcore-web-vitals-measurement\u002Foffloading-work-to-web-workers-with-comlink\u002F","offloading it to a Web Worker"," removes it from the main thread entirely; debouncing then controls how often the worker is asked.",[15,1101,1102],{},[45,1103,52,1106,52,1109,52,1112,52,1114,52,1117,52,1119,52,1123,52,1129,52,1133,52,1137,52,1140,52,1144,52,1148,52,1152,52,1154,52,1158,52,1160,52,1163,52,1166,52,1170,52,1172,52,1175,52,1177,52,1180,52,1183,52,1187,52,1189,52,1192,52,1194,52,1197,52,1199,52,1203,52,1205,52,1208,52,1210,52,1213,52,1216,52,1220,52,1222,52,1225,52,1227,52],{"viewBox":1104,"width":48,"role":49,"ariaLabel":1105,"style":51},"0 0 760 258","Recommended mitigation for common input types, comparing debounce, throttle and yield.",[54,1107],{"className":1108,"x":58,"y":58,"width":48,"height":48,"fill":59},[57],[61,1110,1111],{},"Strategy by input type",[65,1113,1105],{},[54,1115],{"x":69,"y":69,"width":70,"height":1116,"rx":72,"fill":73,"stroke":74,"style":75},"256",[77,1118,1111],{"x":79,"y":80,"fill":74,"style":81},[54,1120],{"x":79,"y":90,"width":1121,"height":1122,"rx":58,"fill":74,"stroke":74,"style":408},"124.6","30.0",[77,1124,1128],{"x":1125,"y":1126,"fill":74,"style":85,"textAnchor":1127},"38.0","75.5","start","Input type",[54,1130],{"x":1131,"y":90,"width":1132,"height":1122,"rx":58,"fill":74,"stroke":74,"style":408},"152.6","289.7",[77,1134,1136],{"x":1135,"y":1126,"fill":74,"style":85,"textAnchor":102},"297.5","Primary technique",[54,1138],{"x":1139,"y":90,"width":1132,"height":1122,"rx":58,"fill":74,"stroke":74,"style":408},"442.3",[77,1141,1143],{"x":1142,"y":1126,"fill":74,"style":85,"textAnchor":102},"587.2","Add if runs exceed 50ms",[54,1145],{"x":79,"y":1146,"width":1121,"height":1122,"rx":58,"fill":73,"stroke":74,"style":1147},"86.0","stroke-opacity:0.35",[77,1149,1151],{"x":1125,"y":1150,"fill":74,"style":85,"textAnchor":1127},"105.5","Search box typing",[54,1153],{"x":1131,"y":1146,"width":1132,"height":1122,"rx":58,"fill":136,"stroke":136,"style":137},[77,1155,1157],{"x":1135,"y":1150,"fill":74,"style":1156,"textAnchor":102},"font-size:12px","debounce 100-200ms",[54,1159],{"x":1139,"y":1146,"width":1132,"height":1122,"rx":58,"fill":73,"stroke":74,"style":1147},[77,1161,1162],{"x":1142,"y":1150,"fill":74,"style":1156,"textAnchor":102},"Yield or worker",[54,1164],{"x":79,"y":1165,"width":1121,"height":1122,"rx":58,"fill":73,"stroke":74,"style":1147},"116.0",[77,1167,1169],{"x":1125,"y":1168,"fill":74,"style":85,"textAnchor":1127},"135.5","Form validation",[54,1171],{"x":1131,"y":1165,"width":1132,"height":1122,"rx":58,"fill":136,"stroke":136,"style":137},[77,1173,1174],{"x":1135,"y":1168,"fill":74,"style":1156,"textAnchor":102},"debounce or on blur",[54,1176],{"x":1139,"y":1165,"width":1132,"height":1122,"rx":58,"fill":73,"stroke":74,"style":1147},[77,1178,1179],{"x":1142,"y":1168,"fill":74,"style":1156,"textAnchor":102},"Validate one field only",[54,1181],{"x":79,"y":1182,"width":1121,"height":1122,"rx":58,"fill":73,"stroke":74,"style":1147},"146.0",[77,1184,1186],{"x":1125,"y":1185,"fill":74,"style":85,"textAnchor":1127},"165.5","Slider or drag",[54,1188],{"x":1131,"y":1182,"width":1132,"height":1122,"rx":58,"fill":136,"stroke":136,"style":137},[77,1190,1191],{"x":1135,"y":1185,"fill":74,"style":1156,"textAnchor":102},"throttle to rAF",[54,1193],{"x":1139,"y":1182,"width":1132,"height":1122,"rx":58,"fill":73,"stroke":74,"style":1147},[77,1195,1196],{"x":1142,"y":1185,"fill":74,"style":1156,"textAnchor":102},"Cheaper preview",[54,1198],{"x":79,"y":99,"width":1121,"height":1122,"rx":58,"fill":73,"stroke":74,"style":1147},[77,1200,1202],{"x":1125,"y":1201,"fill":74,"style":85,"textAnchor":1127},"195.5","Window resize",[54,1204],{"x":1131,"y":99,"width":1132,"height":1122,"rx":58,"fill":136,"stroke":136,"style":137},[77,1206,1207],{"x":1135,"y":1201,"fill":74,"style":1156,"textAnchor":102},"ResizeObserver",[54,1209],{"x":1139,"y":99,"width":1132,"height":1122,"rx":58,"fill":73,"stroke":74,"style":1147},[77,1211,1212],{"x":1142,"y":1201,"fill":74,"style":1156,"textAnchor":102},"Throttle layout work",[54,1214],{"x":79,"y":1215,"width":1121,"height":1122,"rx":58,"fill":73,"stroke":74,"style":1147},"206.0",[77,1217,1219],{"x":1125,"y":1218,"fill":74,"style":85,"textAnchor":1127},"225.5","Autosave",[54,1221],{"x":1131,"y":1215,"width":1132,"height":1122,"rx":58,"fill":136,"stroke":136,"style":137},[77,1223,1224],{"x":1135,"y":1218,"fill":74,"style":1156,"textAnchor":102},"debounce 1-2s",[54,1226],{"x":1139,"y":1215,"width":1132,"height":1122,"rx":58,"fill":73,"stroke":74,"style":1147},[77,1228,1229],{"x":1142,"y":1218,"fill":74,"style":1156,"textAnchor":102},"Idle-time serialisation",[205,1231,1233],{"id":1232},"verification","Verification",[15,1235,1236,1237,1239],{},"Record fast typing (or slider dragging) on a 4x-throttled profile. Every interaction should be under ",[35,1238,165],{},"; no long tasks should appear between keystrokes; and the expensive function should run roughly once per pause, not per keystroke. Count calls with a counter in development builds to confirm the debounce is effective.",[205,1241,1243],{"id":1242},"perceived-latency-not-just-the-metric","Perceived Latency: Not Just the Metric",[15,1245,1246,1247,1250],{},"INP measures the time to the next paint after each input, not the time until the ",[30,1248,1249],{},"result"," is shown. A debounced search can have excellent INP — each keystroke paints the character instantly — and still feel slow if results appear 600ms after the user stops typing. Balance the two: keep the input echo instant, show immediate partial feedback (a spinner in the field, cached prefix matches), and keep the debounce short. The goal is a page that is both measurably responsive and perceptibly quick; the metric captures only the first half.",[205,1252,1254],{"id":1253},"testing-the-strategy-under-realistic-typing","Testing the Strategy Under Realistic Typing",[15,1256,1257],{},"Synthetic tests that type with a fixed 50ms delay do not resemble humans, who type in bursts with pauses between words. Replay recorded keystroke timings — collect inter-key intervals from a few sessions in development — when testing debounce delays, and test on a throttled profile. A debounce tuned on a fast laptop with robotic typing tends to fire mid-word on real devices, reintroducing exactly the long tasks it was meant to avoid.",[205,1259,1261],{"id":1260},"faq","FAQ",[1263,1264,1267,1271],"details",{"className":1265},[1266],"faq-item",[1268,1269,1270],"summary",{},"Is requestIdleCallback a better debounce?",[15,1272,1273],{},"It answers a different question — \"run when the browser is idle\" rather than \"run after input pauses\". On a busy page idle periods may be rare, delaying results indefinitely; on an idle page it runs almost immediately, providing no debouncing at all. Use it for genuinely optional work like prefetching, not for results the user is waiting for.",[1263,1275,1277,1280],{"className":1276},[1266],[1268,1278,1279],{},"What debounce delay is best for search?",[15,1281,1282],{},"Typically 100–250ms. Shorter delays compute more stale results; longer delays make the UI feel unresponsive. Measure your users' inter-keystroke intervals — many type at 150–250ms between keys — and set the delay just above the typical interval so most mid-word keystrokes are absorbed.",[1263,1284,1286,1289],{"className":1285},[1266],[1268,1287,1288],{},"Can I debounce inside a framework's onChange without breaking controlled inputs?",[15,1290,1291],{},"Yes, as long as only the expensive consequence is debounced, not the input's own state update. Update the controlled value synchronously so the character appears immediately, and pass the value to a debounced function for filtering or fetching. Debouncing the state update itself makes the input lag behind typing, which users notice immediately and which INP cannot detect because each keystroke still paints quickly.",[205,1293,1295],{"id":1294},"related","Related",[210,1297,1298,1305,1312],{},[213,1299,1300,1304],{},[19,1301,1303],{"href":1302},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Ffixing-slow-inp-on-text-inputs-and-typeahead\u002F","Fixing slow INP on text inputs and typeahead"," — applying these techniques to search fields.",[213,1306,1307,1311],{},[19,1308,1310],{"href":1309},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fscheduler-yield-vs-settimeout-0-for-yielding\u002F","scheduler.yield() vs setTimeout(0) for yielding"," — choosing the yield primitive.",[213,1313,1314,1318],{},[19,1315,1317],{"href":1316},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fusing-scheduler-posttask-priorities\u002F","Using scheduler.postTask() priorities"," — declaring which work can wait.",[1320,1321,1323],"script",{"type":1322},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Debouncing vs Yielding for Input Events: Which One Fixes INP?\",\n  \"description\": \"When to debounce, when to throttle, and when to yield inside the work — and why the wrong choice leaves INP above 200ms.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Debounce redundant work, with cancellation\",\n      \"text\": \"Expected outcome: the expensive work runs once per pause instead of once per keystroke.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Yield inside the work, checking for cancellation\",\n      \"text\": \"Expected outcome: no task over 50ms, and stale runs stop at the next yield point rather than running to completion.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Throttle continuous inputs to the frame rate\",\n      \"text\": \"For sliders, drag handles and scroll-linked UI, update at most once per frame and keep each update cheap.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Move genuinely heavy work to a worker\",\n      \"text\": \"If even chunked work is too much CPU for the device, offloading it to a Web Worker removes it from the main thread entirely; debouncing then controls how often the worker is asked.\"\n    }\n  ]\n}\n",[1320,1325,1326],{"type":1322},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Debouncing vs Yielding for Input Events: Which One Fixes INP?\",\n  \"description\": \"When to debounce, when to throttle, and when to yield inside the work — and why the wrong choice leaves INP above 200ms.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Fdebouncing-vs-yielding-for-input-events\u002F\"\n  }\n}\n",[1320,1328,1329],{"type":1322},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Core Web Vitals & Measurement\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Profiling Event Handlers for INP\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Debouncing vs Yielding for Input Events\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Fdebouncing-vs-yielding-for-input-events\u002F\"\n    }\n  ]\n}\n",[1331,1332,1333],"style",{},"html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}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 .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}",{"title":514,"searchDepth":548,"depth":548,"links":1335},[1336,1337,1338,1339,1345,1346,1347,1348,1349],{"id":207,"depth":548,"text":208},{"id":244,"depth":548,"text":245},{"id":341,"depth":548,"text":342},{"id":501,"depth":548,"text":502,"children":1340},[1341,1342,1343,1344],{"id":506,"depth":557,"text":507},{"id":722,"depth":557,"text":723},{"id":959,"depth":557,"text":960},{"id":1090,"depth":557,"text":1091},{"id":1232,"depth":548,"text":1233},{"id":1242,"depth":548,"text":1243},{"id":1253,"depth":548,"text":1254},{"id":1260,"depth":548,"text":1261},{"id":1294,"depth":548,"text":1295},"When to debounce, when to throttle, and when to yield inside the work — and why the wrong choice leaves INP above 200ms.","md",{"slug":1353,"type":1354,"breadcrumb":1355,"datePublished":1363,"dateModified":1363},"debouncing-vs-yielding-for-input-events","article",[1356,1359,1360,1361],{"name":1357,"url":1358},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1362},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Fdebouncing-vs-yielding-for-input-events\u002F","2026-10-06",true,"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Fdebouncing-vs-yielding-for-input-events",{"title":1367,"description":1368},"Debouncing vs Yielding for Input Events and INP","Debouncing cuts how often expensive work runs; yielding splits it so input can interleave. Compare their effect on INP, throughput and perceived lag.","core-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Fdebouncing-vs-yielding-for-input-events\u002Findex","6fmMD_qn99PDk1H3UZX4zSXjB0_ewm8CK2SsHq4fFCU",[1372,1375],{"title":22,"path":1373,"stem":1374,"children":-1},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp","core-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Findex",{"title":1376,"path":1377,"stem":1378,"children":-1},"Finding the Slowest Interaction in the Performance Panel","\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Ffinding-the-slowest-interaction-in-the-performance-panel","core-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Ffinding-the-slowest-interaction-in-the-performance-panel\u002Findex",1791308073616]