[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"search-suggestions":44,"article-weekly-debounce-and-throttle":92,"related-weekly-debounce-and-throttle":485,"code:js:true:1yzjfu8":612,"code:js:true:ubyjed":613,"code:js:true:16bvy32":614,"code:js:true:16b9fcr":615,"code:js:true:jgsrqb":616,"code:jsx:true:1e3yl2":617,"code:jsx:true:h9zaap":618,"code:js:true:1xpa3e5":619,"comments-019ff73a-fb5b-73ce-b725-2912167a7dde":620},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,[45,49,53,57,61,65,69,73,77,81,85,89],{"slug":46,"name":47,"articles":48},"webdev","Webdev",75,{"slug":50,"name":51,"articles":52},"frontend","Frontend",64,{"slug":54,"name":55,"articles":56},"javascript","Javascript",63,{"slug":58,"name":59,"articles":60},"css","Css",29,{"slug":62,"name":63,"articles":64},"tutorial","Tutorial",15,{"slug":66,"name":67,"articles":68},"typescript","Typescript",11,{"slug":70,"name":71,"articles":72},"performance","Performance",10,{"slug":74,"name":75,"articles":76},"react","React",7,{"slug":78,"name":79,"articles":80},"grammar","Grammar",6,{"slug":82,"name":83,"articles":84},"node","Node",5,{"slug":86,"name":87,"articles":88},"programming","Programming",4,{"slug":90,"name":91,"articles":88},"html","Html",{"id":93,"slug":94,"title":95,"subtitle":96,"excerpt":97,"coverUrl":98,"locale":13,"readingMinutes":99,"publishedAt":100,"viewCount":101,"likeCount":19,"commentCount":19,"author":102,"vertical":107,"topic":108,"tags":110,"_count":115,"playground":117,"body":119,"bodyMd":444,"seo":445,"translationGroupId":447,"series":448,"podcastUrl":96,"verticalId":5,"thread":473,"assessments":475,"translations":481,"quiz":483},"019ff73a-fb5b-73ce-b725-2912167a7dde","weekly-debounce-and-throttle","Debounce and Throttle in JavaScript: The Complete Guide",null,"Learn debounce and throttle in JavaScript step by step, with worked examples, edge cases, and a copy-paste cheat sheet for search, scroll, and resize.","\u002Fmedia\u002Fcovers\u002Fweekly-debounce-and-throttle.png",14,"2026-08-31T13:12:14.139Z",27,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":54,"name":109},"JavaScript",[111,112,113,114],{"slug":54,"name":55,"color":96},{"slug":46,"name":47,"color":96},{"slug":62,"name":63,"color":96},{"slug":70,"name":71,"color":96},{"assessments":116},1,{"slug":94,"title":118},"Debounce vs throttle — interactive playground",{"blocks":120,"version":116},[121,125,130,133,142,145,148,163,166,169,175,178,181,184,187,190,195,198,201,204,208,212,215,218,221,225,228,231,234,237,241,244,247,250,254,257,260,263,268,271,275,278,281,291,294,297,300,303,306,309,313,316,320,323,326,329,333,336,339,342,345,381,385,388,391,394,397,400,403,411,414,417,420,423,426,429,432,435,438],{"id":122,"html":123,"type":124},"b1","\u003Cp>Type &quot;javascript&quot; into an unthrottled search box and watch the network tab: an HTTP request for \u003Ccode>j\u003C\u002Fcode>, another for \u003Ccode>ja\u003C\u002Fcode>, another for \u003Ccode>jav\u003C\u002Fcode>, ten in total, nine of them thrown away before the response even lands. The server did ten times the work the feature needed, and the UI flickers with results for a query the user abandoned two keystrokes ago. The fix is two small, deceptively simple functions — debounce and throttle — and the part that trips people up isn&#39;t writing them, it&#39;s knowing exactly when each one fires.\u003C\u002Fp>","paragraph",{"id":126,"html":127,"text":128,"type":129,"level":31},"b2","What you&#39;ll learn","What you'll learn","heading",{"id":131,"html":132,"type":124},"b3","\u003Cp>By the end of this guide you&#39;ll be able to:\u003C\u002Fp>",{"id":134,"type":135,"items":136,"ordered":18},"b4","list",[137,138,139,140,141],"Explain the precise difference between debounce and throttle, not just &quot;they both slow things down&quot;","Write a correct debounce and a correct throttle from scratch, including leading\u002Ftrailing edge behavior","Choose the right one for search inputs, scroll handlers, resize handlers, and button clicks","Avoid the memory leaks and stale-closure bugs both patterns cause in React and vanilla JS alike","Use a copy-paste utility with \u003Ccode>cancel()\u003C\u002Fcode> and \u003Ccode>flush()\u003C\u002Fcode> support",{"id":143,"html":144,"type":124},"b5","\u003Cp>\u003Cstrong>Who this is for:\u003C\u002Fstrong> you write JavaScript day to day, you&#39;ve attached an event listener before, and you&#39;ve either hand-rolled a \u003Ccode>setTimeout\u003C\u002Fcode> hack for this exact problem or reached for lodash without fully trusting what its defaults do.\u003C\u002Fp>",{"id":146,"html":147,"text":147,"type":129,"level":31},"b6","Contents",{"id":149,"type":135,"items":150,"ordered":18},"b7",[151,152,153,154,155,156,157,158,159,160,161,162],"\u003Ca href=\"#why-debounce-and-throttle-exist\">Why debounce and throttle exist\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-a-bouncer-not-a-filter\">The mental model: a bouncer, not a filter\u003C\u002Fa>","\u003Ca href=\"#stage-1-debounce-from-scratch\">Stage 1: debounce from scratch\u003C\u002Fa>","\u003Ca href=\"#stage-2-throttle-from-scratch\">Stage 2: throttle from scratch\u003C\u002Fa>","\u003Ca href=\"#stage-3-leading-and-trailing-edges\">Stage 3: leading and trailing edges\u003C\u002Fa>","\u003Ca href=\"#stage-4-cancel-flush-and-cleanup\">Stage 4: cancel, flush, and cleanup\u003C\u002Fa>","\u003Ca href=\"#stage-5-using-them-in-react\">Stage 5: using them in React\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices-when-not-to-use-them\">Best practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key takeaways\u003C\u002Fa>",{"id":164,"html":165,"text":165,"type":129,"level":31},"b8","Why debounce and throttle exist",{"id":167,"html":168,"type":124},"b9","\u003Cp>Here&#39;s the naive version of a live search box — the one almost everyone writes first:\u003C\u002Fp>",{"id":170,"code":171,"type":172,"language":173,"highlight":174},"b10","\u002F\u002F the wrong way — fires a request on every single keystroke\nsearchInput.addEventListener(\"input\", (event) => {\n  fetchResults(event.target.value); \u002F\u002F one HTTP request per keystroke\n});","code","js",[],{"id":176,"html":177,"type":124},"b11","\u003Cp>Type a six-letter word at a normal pace and this fires six requests in well under a second, five of which are already obsolete by the time their responses arrive. Worse, network responses don&#39;t always resolve in the order they were sent — a slow response for \u003Ccode>jav\u003C\u002Fcode> can arrive \u003Cem>after\u003C\u002Fem> the fast response for \u003Ccode>javascript\u003C\u002Fcode>, and now the screen is showing stale results for a query the user already replaced. The bug isn&#39;t visible in a quick manual test because your laptop and the API are both fast; it shows up for a real user on a real network, and by then it looks like &quot;search is flaky&quot; rather than &quot;search fires way too often.&quot;\u003C\u002Fp>",{"id":179,"html":180,"type":124},"b12","\u003Cp>The same shape of problem hits scroll and resize handlers, just with a different failure mode. A \u003Ccode>scroll\u003C\u002Fcode> event can fire dozens of times per second. If the handler does anything nontrivial — reading \u003Ccode>getBoundingClientRect()\u003C\u002Fcode>, updating layout, running a chunk of business logic — the page starts dropping frames and scrolling turns jerky, even though nothing is technically &quot;broken.&quot;\u003C\u002Fp>",{"id":182,"html":183,"type":124},"b13","\u003Cp>Both problems come from the same root cause: \u003Cstrong>the event source fires far more often than the response actually needs to run.\u003C\u002Fstrong> Debounce and throttle are two different answers to &quot;how often is often enough,&quot; and they are not interchangeable.\u003C\u002Fp>",{"id":185,"html":186,"text":186,"type":129,"level":31},"b14","The mental model: a bouncer, not a filter",{"id":188,"html":189,"type":124},"b15","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> neither function \u003Cem>filters\u003C\u002Fem> events — every event still reaches your wrapper and every event still runs a check. What changes is how often the check lets the real work through, and the two functions use opposite strategies for deciding that.\u003C\u002Fp>",{"id":191,"type":135,"items":192,"ordered":18},"b16",[193,194],"\u003Cstrong>Debounce says &quot;wait for quiet.&quot;\u003C\u002Fstrong> Every call resets a timer. The wrapped function only runs once the calls actually \u003Cem>stop\u003C\u002Fem> for the configured delay. Think of an elevator door: every time someone walks up, the door resets its close timer. It only closes once nobody has approached it for a few seconds.","\u003Cstrong>Throttle says &quot;at most once per interval.&quot;\u003C\u002Fstrong> It doesn&#39;t care whether calls are still coming in — it just refuses to let the wrapped function run again until a fixed amount of time has passed since the last time it ran. Think of a metronome, or a bouncer who lets one person through the door every two seconds regardless of how long the line is.",{"id":196,"html":197,"type":124},"b17","\u003Cp>That single distinction — &quot;wait for silence&quot; versus &quot;space it out at a fixed rate&quot; — explains almost every behavior difference in the rest of this guide. Debounce is right when you only care about the \u003Cem>final\u003C\u002Fem> state (the finished search query). Throttle is right when you need \u003Cem>regular updates during\u003C\u002Fem> continuous activity (a scroll position that should keep updating while the user scrolls).\u003C\u002Fp>",{"id":199,"html":200,"text":200,"type":129,"level":31},"b18","Stage 1: debounce from scratch",{"id":202,"html":203,"type":124},"b19","\u003Cp>The smallest correct debounce is short — a closure holding one timer ID:\u003C\u002Fp>",{"id":205,"code":206,"type":172,"language":173,"highlight":207},"b20","function debounce(fn, delayMs) {\n  let timeoutId; \u002F\u002F lives across calls thanks to the closure\n  return function debounced(...args) {\n    clearTimeout(timeoutId); \u002F\u002F cancel whatever was pending\n    timeoutId = setTimeout(() => fn.apply(this, args), delayMs);\n  };\n}\n\nconst debouncedSearch = debounce((query) => fetchResults(query), 300);\nsearchInput.addEventListener(\"input\", (e) => debouncedSearch(e.target.value));",[],{"id":209,"html":210,"type":211},"b21","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> every call to \u003Ccode>debounced\u003C\u002Fcode> cancels the previous pending timer and starts a new one. \u003Ccode>fn\u003C\u002Fcode> only ever actually runs if 300ms pass with \u003Cem>no\u003C\u002Fem> new call in between — which is exactly &quot;wait for quiet,&quot; implemented as literally as possible.\u003C\u002Fp>\n","quote",{"id":213,"html":214,"type":124},"b22","\u003Cp>Type &quot;js&quot; quickly and \u003Ccode>debounced\u003C\u002Fcode> runs twice (once per keystroke) but \u003Ccode>fn\u003C\u002Fcode> runs zero times until you stop — then it runs exactly once, 300ms after your last keystroke, with the final value of \u003Ccode>query\u003C\u002Fcode>. That&#39;s the whole mechanism. Everything else in this guide is a variation on this six-line function.\u003C\u002Fp>",{"id":216,"html":217,"text":217,"type":129,"level":31},"b23","Stage 2: throttle from scratch",{"id":219,"html":220,"type":124},"b24","\u003Cp>Throttle needs to track \u003Cem>when it last ran\u003C\u002Fem>, not whether a timer is pending:\u003C\u002Fp>",{"id":222,"code":223,"type":172,"language":173,"highlight":224},"b25","function throttle(fn, intervalMs) {\n  let lastRun = 0; \u002F\u002F timestamp of the last time fn actually executed\n  return function throttled(...args) {\n    const now = Date.now();\n    if (now - lastRun >= intervalMs) {\n      lastRun = now;\n      fn.apply(this, args);\n    }\n  };\n}\n\nconst throttledOnScroll = throttle(() => updateScrollProgress(), 100);\nwindow.addEventListener(\"scroll\", throttledOnScroll);",[],{"id":226,"html":227,"type":211},"b26","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> this implementation runs \u003Ccode>fn\u003C\u002Fcode> immediately on the very first call (because \u003Ccode>now - lastRun\u003C\u002Fcode> starts effectively infinite), then ignores every call until \u003Ccode>intervalMs\u003C\u002Fcode> has elapsed, at which point the next call through gets to run. Calls that arrive during the &quot;cooldown&quot; are dropped entirely — not queued, not delayed, just discarded.\u003C\u002Fp>\n",{"id":229,"html":230,"type":124},"b27","\u003Cp>That last detail matters: with this specific implementation, if the burst of calls stops \u003Cem>during\u003C\u002Fem> a cooldown window, the very last call in the burst is simply lost — \u003Ccode>fn\u003C\u002Fcode> doesn&#39;t get one final run with the latest arguments. Stage 3 fixes that.\u003C\u002Fp>",{"id":232,"html":233,"text":233,"type":129,"level":31},"b28","Stage 3: leading and trailing edges",{"id":235,"html":236,"type":124},"b29","\u003Cp>&quot;Leading edge&quot; means running on the \u003Cem>first\u003C\u002Fem> call in a burst; &quot;trailing edge&quot; means running once more after the burst ends, with the latest arguments. The debounce in Stage 1 is trailing-only. The throttle in Stage 2 is leading-only. A production-grade version usually supports both, because dropping the trailing call (throttle) or delaying every call including the first one (debounce) is sometimes the wrong tradeoff:\u003C\u002Fp>",{"id":238,"code":239,"type":172,"language":173,"highlight":240},"b30","function throttle(fn, intervalMs, { leading = true, trailing = true } = {}) {\n  let lastRun = 0;\n  let timeoutId = null;\n  let lastArgs = null;\n\n  return function throttled(...args) {\n    const now = Date.now();\n    const remaining = intervalMs - (now - lastRun);\n    lastArgs = args;\n\n    if (remaining \u003C= 0) {\n      if (leading || lastRun !== 0) {\n        lastRun = now;\n        fn.apply(this, args);\n      }\n    } else if (trailing && !timeoutId) {\n      timeoutId = setTimeout(() => {\n        timeoutId = null;\n        lastRun = Date.now();\n        fn.apply(this, lastArgs);\n      }, remaining);\n    }\n  };\n}",[],{"id":242,"html":243,"type":211},"b31","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>leading\u003C\u002Fcode> controls whether the very first call in a burst runs immediately; \u003Ccode>trailing\u003C\u002Fcode> controls whether one extra call fires after the burst goes quiet, using whatever arguments arrived last. lodash&#39;s \u003Ccode>_.throttle\u003C\u002Fcode> defaults to \u003Ccode>{ leading: true, trailing: true }\u003C\u002Fcode>, and its \u003Ccode>_.debounce\u003C\u002Fcode> defaults to \u003Ccode>{ leading: false, trailing: true }\u003C\u002Fcode> — which is exactly why debounce &quot;feels like&quot; it only fires at the end, while throttle &quot;feels like&quot; it fires immediately and then periodically.\u003C\u002Fp>\n",{"id":245,"html":246,"text":246,"type":129,"level":31},"b32","Stage 4: cancel, flush, and cleanup",{"id":248,"html":249,"type":124},"b33","\u003Cp>A debounce or throttle you can&#39;t cancel is a liability the moment its owner disappears — a component unmounts, a modal closes, a request is superseded. Attach the controls directly to the returned function:\u003C\u002Fp>",{"id":251,"code":252,"type":172,"language":173,"highlight":253},"b34","function debounce(fn, delayMs) {\n  let timeoutId;\n  function debounced(...args) {\n    clearTimeout(timeoutId);\n    timeoutId = setTimeout(() => fn.apply(this, args), delayMs);\n  }\n  debounced.cancel = () => clearTimeout(timeoutId); \u002F\u002F drop the pending call\n  return debounced;\n}\n\nconst debouncedSave = debounce(saveDraft, 500);\ndebouncedSave(\"draft text\");\ndebouncedSave.cancel(); \u002F\u002F \"draft text\" will never be saved",[],{"id":255,"html":256,"type":124},"b35","\u003Cp>\u003Ccode>flush()\u003C\u002Fcode> is the mirror image: run the pending call \u003Cem>right now\u003C\u002Fem> instead of waiting or dropping it — useful when the user explicitly submits a form while a debounced autosave is still pending, so the two writes don&#39;t race.\u003C\u002Fp>",{"id":258,"html":259,"text":259,"type":129,"level":31},"b36","Stage 5: using them in React",{"id":261,"html":262,"type":124},"b37","\u003Cp>The trap in React isn&#39;t the debounce function itself — it&#39;s \u003Cem>where\u003C\u002Fem> you create it. Creating a new debounced function on every render breaks the whole mechanism, because each render&#39;s closure has no memory of the previous render&#39;s timer:\u003C\u002Fp>",{"id":264,"code":265,"type":172,"language":266,"highlight":267},"b38","\u002F\u002F the wrong way — a brand-new debounce (and a brand-new timer) every render\nfunction SearchBox() {\n  const [query, setQuery] = useState(\"\");\n  const debouncedSearch = debounce((q) => fetchResults(q), 300); \u002F\u002F recreated every render\n\n  return (\n    \u003Cinput\n      value={query}\n      onChange={(e) => {\n        setQuery(e.target.value);\n        debouncedSearch(e.target.value); \u002F\u002F this instance's timer never gets to fire before a new one replaces it\n      }}\n    \u002F>\n  );\n}","jsx",[],{"id":269,"html":270,"type":124},"b39","\u003Cp>Create the debounced function once, with \u003Ccode>useMemo\u003C\u002Fcode> or \u003Ccode>useRef\u003C\u002Fcode>, and cancel it on unmount:\u003C\u002Fp>",{"id":272,"code":273,"type":172,"language":266,"highlight":274},"b40","function SearchBox() {\n  const [query, setQuery] = useState(\"\");\n  const debouncedSearch = useMemo(() => debounce((q) => fetchResults(q), 300), []);\n\n  useEffect(() => {\n    return () => debouncedSearch.cancel(); \u002F\u002F no fetch after this component is gone\n  }, [debouncedSearch]);\n\n  return (\n    \u003Cinput\n      value={query}\n      onChange={(e) => {\n        setQuery(e.target.value);\n        debouncedSearch(e.target.value);\n      }}\n    \u002F>\n  );\n}",[],{"id":276,"html":277,"type":211},"b41","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> the debounced function must outlive individual renders (created once, stored in a ref or memoized with an empty dependency array) and must be explicitly cancelled in a cleanup function, or its pending timer will call \u003Ccode>fetchResults\u003C\u002Fcode> on state that no longer exists.\u003C\u002Fp>\n",{"id":279,"html":280,"text":280,"type":129,"level":31},"b42","Edge cases and gotchas",{"id":282,"type":135,"items":283,"ordered":18},"b43",[284,285,286,287,288,289,290],"\u003Cstrong>Stale closures.\u003C\u002Fstrong> If \u003Ccode>fn\u003C\u002Fcode> inside a debounce\u002Fthrottle closes over a variable that changes between calls (a piece of state, a prop), the call that eventually fires can use an outdated value. \u003Ccode>useCallback\u003C\u002Fcode>\u002F\u003Ccode>useRef\u003C\u002Fcode> patterns exist specifically to solve this in React; in vanilla JS, pass the current value as an argument rather than relying on the closure.","\u003Cstrong>\u003Ccode>this\u003C\u002Fcode> binding.\u003C\u002Fstrong> The hand-written implementations above use \u003Ccode>fn.apply(this, args)\u003C\u002Fcode> so a debounced\u002Fthrottled \u003Cem>method\u003C\u002Fem> still sees the right \u003Ccode>this\u003C\u002Fcode>. If you strip that out, calling \u003Ccode>debounce(obj.method, 300)()\u003C\u002Fcode> silently breaks \u003Ccode>this\u003C\u002Fcode> inside \u003Ccode>method\u003C\u002Fcode>.","\u003Cstrong>Debouncing an \u003Ccode>async\u003C\u002Fcode> function doesn&#39;t cancel in-flight work.\u003C\u002Fstrong> Debounce delays \u003Cem>calling\u003C\u002Fem> the function; it does nothing about a \u003Ccode>fetch\u003C\u002Fcode> that&#39;s already in progress from a previous call. Pair debounce with an \u003Ccode>AbortController\u003C\u002Fcode> if a slow, superseded request could still resolve and overwrite a newer one.","\u003Cstrong>Race conditions between the trailing call and unmount.\u003C\u002Fstrong> A debounce&#39;s \u003Ccode>setTimeout\u003C\u002Fcode> keeps a reference to \u003Ccode>fn\u003C\u002Fcode> alive even after the component that created it is gone. Without \u003Ccode>cancel()\u003C\u002Fcode> on cleanup, the trailing call still fires and can throw (&quot;cannot update state on an unmounted component&quot;) or write to a resource that no longer applies.","\u003Cstrong>Throttle intervals and animation.\u003C\u002Fstrong> A \u003Ccode>scroll\u003C\u002Fcode> or \u003Ccode>mousemove\u003C\u002Fcode> throttle set to a fixed millisecond interval can visibly stutter, because it&#39;s not synchronized with the browser&#39;s paint cycle. For anything visual, prefer \u003Ccode>requestAnimationFrame\u003C\u002Fcode>-based throttling: run at most once per frame instead of once per N milliseconds.","\u003Cstrong>Testing.\u003C\u002Fstrong> Both patterns depend on real time passing, which makes tests flaky if you \u003Ccode>sleep\u003C\u002Fcode>. Use fake timers (\u003Ccode>jest.useFakeTimers()\u003C\u002Fcode> \u002F \u003Ccode>vi.useFakeTimers()\u003C\u002Fcode>) and advance them explicitly (\u003Ccode>jest.advanceTimersByTime(300)\u003C\u002Fcode>) instead of waiting on the wall clock.","\u003Cstrong>Debounce delay versus perceived responsiveness.\u003C\u002Fstrong> A 300ms debounce feels instant to most users; anything above ~500ms on a search-as-you-type field starts to feel sluggish, because the user has already mentally &quot;sent&quot; the query.",{"id":292,"html":293,"text":293,"type":129,"level":31},"b44","Best practices (when (not) to use them)",{"id":295,"html":296,"type":124},"b45","\u003Cp>\u003Cstrong>Reach for debounce when\u003C\u002Fstrong> you only care about the value once activity settles: search-as-you-type, autosave, form validation that shouldn&#39;t run on every keystroke, resize-triggered layout recalculation where only the final size matters.\u003C\u002Fp>",{"id":298,"html":299,"type":124},"b46","\u003Cp>\u003Cstrong>Reach for throttle when\u003C\u002Fstrong> you need periodic updates \u003Cem>during\u003C\u002Fem> continuous activity, not just at the end: scroll-position tracking, a progress indicator following \u003Ccode>mousemove\u003C\u002Fcode>, rate-limiting how often a &quot;user is typing&quot; indicator pings a server, infinite-scroll trigger checks.\u003C\u002Fp>",{"id":301,"html":302,"type":124},"b47","\u003Cp>\u003Cstrong>Avoid both when\u003C\u002Fstrong> the action needs to feel instantaneous every single time — a button click, an &quot;add to cart,&quot; a keyboard shortcut. Delaying or dropping those erodes trust in the UI even if it&#39;s technically more &quot;efficient.&quot; If a click handler is slow, fix the handler; don&#39;t debounce the click.\u003C\u002Fp>",{"id":304,"html":305,"type":124},"b48","\u003Cp>\u003Cstrong>Don&#39;t stack them by accident.\u003C\u002Fstrong> Wrapping an already-throttled handler in another library&#39;s debounce (or vice versa) is a common cause of &quot;my scroll handler feels randomly laggy&quot; bugs — pick one strategy per event source and be deliberate about the delay.\u003C\u002Fp>",{"id":307,"html":308,"text":308,"type":129,"level":31},"b49","FAQ",{"id":310,"html":311,"text":312,"type":129,"level":43},"b50","What&#39;s the actual difference between debounce and throttle?","What's the actual difference between debounce and throttle?",{"id":314,"html":315,"type":124},"b51","\u003Cp>Debounce waits for a pause in activity and then runs once; throttle runs at a fixed maximum rate regardless of whether activity is still ongoing. Debounce answers &quot;what&#39;s the final state?&quot;; throttle answers &quot;give me periodic updates while this keeps happening.&quot;\u003C\u002Fp>",{"id":317,"html":318,"text":319,"type":129,"level":43},"b52","Does lodash&#39;s \u003Ccode>_.debounce\u003C\u002Fcode> behave differently from a hand-rolled one?","Does lodash's .debounce behave differently from a hand-rolled one?",{"id":321,"html":322,"type":124},"b53","\u003Cp>Functionally, a correct hand-rolled trailing-edge debounce matches \u003Ccode>_.debounce\u003C\u002Fcode>&#39;s default behavior (\u003Ccode>leading: false, trailing: true\u003C\u002Fcode>). lodash additionally ships \u003Ccode>cancel()\u003C\u002Fcode>, \u003Ccode>flush()\u003C\u002Fcode>, and a \u003Ccode>maxWait\u003C\u002Fcode> option (a ceiling on how long calls can be delayed even under continuous activity) — genuinely useful extras, not a different core algorithm.\u003C\u002Fp>",{"id":324,"html":325,"text":325,"type":129,"level":43},"b54","Can I debounce an async function?",{"id":327,"html":328,"type":124},"b55","\u003Cp>Yes, but debounce only controls \u003Cem>when the call happens\u003C\u002Fem> — it has no knowledge of what the async function does afterward. If an earlier (superseded) call&#39;s promise resolves after a later one, you can still get out-of-order results unless you also track &quot;is this the latest call&quot; or cancel the earlier request with an \u003Ccode>AbortController\u003C\u002Fcode>.\u003C\u002Fp>",{"id":330,"html":331,"text":332,"type":129,"level":43},"b56","Should I use \u003Ccode>requestAnimationFrame\u003C\u002Fcode> instead of throttle for scroll or resize?","Should I use requestAnimationFrame instead of throttle for scroll or resize?",{"id":334,"html":335,"type":124},"b57","\u003Cp>For anything that updates visuals (position, size, opacity), yes — \u003Ccode>requestAnimationFrame\u003C\u002Fcode> throttling caps the work at once per paint, which is both smoother and never more work than the browser can actually display. Millisecond-based throttling is still the right tool for non-visual rate-limiting, like capping how often you ping an analytics endpoint.\u003C\u002Fp>",{"id":337,"html":338,"text":338,"type":129,"level":43},"b58","How do I cancel a debounced or throttled call?",{"id":340,"html":341,"type":124},"b59","\u003Cp>Attach a \u003Ccode>.cancel()\u003C\u002Fcode> method to the returned function (Stage 4) and call it — in \u003Ccode>setTimeout\u003C\u002Fcode>&#39;s case that&#39;s a \u003Ccode>clearTimeout\u003C\u002Fcode>; for lodash, \u003Ccode>_.debounce\u003C\u002Fcode> and \u003Ccode>_.throttle\u003C\u002Fcode> both return functions with a built-in \u003Ccode>.cancel()\u003C\u002Fcode>.\u003C\u002Fp>",{"id":343,"html":344,"text":344,"type":129,"level":31},"b60","Cheat sheet",{"id":346,"head":347,"rows":351,"type":380},"b61",[348,349,350],"Task","Code","Notes",[352,356,360,364,368,372,376],[353,354,355],"Debounce (trailing only)","\u003Ccode>debounce(fn, 300)\u003C\u002Fcode>","Runs once, 300ms after calls stop. Best for search\u002Fautosave.",[357,358,359],"Throttle (leading + trailing)","\u003Ccode>throttle(fn, 100, { leading: true, trailing: true })\u003C\u002Fcode>","Runs immediately, then at most every 100ms, plus once more after the burst ends.",[361,362,363],"Cancel a pending call","\u003Ccode>debounced.cancel()\u003C\u002Fcode>","Clears the timer; the delayed call never runs.",[365,366,367],"Flush a pending call now","\u003Ccode>debounced.flush()\u003C\u002Fcode>","Runs the pending call immediately instead of waiting.",[369,370,371],"Visual\u002Fanimation rate-limit","\u003Ccode>requestAnimationFrame\u003C\u002Fcode> loop","Caps work to once per paint; smoother than a fixed-ms throttle for scroll\u002Fresize.",[373,374,375],"React: create once","\u003Ccode>useMemo(() =&gt; debounce(fn, ms), [])\u003C\u002Fcode>","Never recreate inside the render body.",[377,378,379],"React: cleanup","\u003Ccode>useEffect(() =&gt; () =&gt; debounced.cancel(), [])\u003C\u002Fcode>","Prevents calls firing after unmount.","table",{"id":382,"code":383,"type":172,"language":173,"highlight":384},"b62","\u002F\u002F the whole pattern, copy-paste ready\nfunction debounce(fn, delayMs) {\n  let timeoutId;\n  function debounced(...args) {\n    clearTimeout(timeoutId);\n    timeoutId = setTimeout(() => fn.apply(this, args), delayMs);\n  }\n  debounced.cancel = () => clearTimeout(timeoutId);\n  return debounced;\n}\n\nfunction throttle(fn, intervalMs, { leading = true, trailing = true } = {}) {\n  let lastRun = 0;\n  let timeoutId = null;\n  let lastArgs = null;\n\n  function throttled(...args) {\n    const now = Date.now();\n    const remaining = intervalMs - (now - lastRun);\n    lastArgs = args;\n\n    if (remaining \u003C= 0) {\n      if (leading || lastRun !== 0) {\n        lastRun = now;\n        fn.apply(this, args);\n      }\n    } else if (trailing && !timeoutId) {\n      timeoutId = setTimeout(() => {\n        timeoutId = null;\n        lastRun = Date.now();\n        fn.apply(this, lastArgs);\n      }, remaining);\n    }\n  }\n  throttled.cancel = () => {\n    clearTimeout(timeoutId);\n    timeoutId = null;\n  };\n  return throttled;\n}",[],{"id":386,"html":387,"type":124},"b63","\u003C!-- playground:start -->",{"id":389,"html":390,"text":390,"type":129,"level":31},"b64","🎮 Try it yourself",{"id":392,"html":393,"type":124},"b65","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-debounce-and-throttle\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":395,"html":396,"type":124},"b66","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":398,"html":399,"type":124},"b67","\u003C!-- playground:end -->",{"id":401,"html":402,"text":402,"type":129,"level":31},"b68","Key takeaways",{"id":404,"type":135,"items":405,"ordered":18},"b69",[406,407,408,409,410],"Debounce waits for quiet and runs once at the end; throttle runs on a fixed schedule no matter how long the activity continues.","Every event still reaches the wrapper — what changes is how often the \u003Cem>real\u003C\u002Fem> work behind it is allowed to run.","Use debounce for &quot;what&#39;s the final value&quot; (search, autosave); use throttle (or \u003Ccode>requestAnimationFrame\u003C\u002Fcode>) for &quot;keep me updated while this continues&quot; (scroll, resize, drag).","Both need \u003Ccode>cancel()\u003C\u002Fcode> wired into cleanup — an uncancelled debounce or throttle is a call waiting to fire on a component that no longer exists.","Create the wrapped function once, not on every render.",{"id":412,"html":413,"type":124},"b70","\u003C!-- quiz:start -->",{"id":415,"html":416,"text":416,"type":129,"level":31},"b71","🧠 Test yourself",{"id":418,"html":419,"type":124},"b72","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-debounce-and-throttle\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":421,"html":422,"type":124},"b73","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":424,"html":425,"type":124},"b74","\u003C!-- quiz:end -->",{"id":427,"html":428,"type":124},"b75","\u003Cp>That search box from the opening paragraph needs exactly one line changed — wrap the handler in \u003Ccode>debounce(fetchResults, 300)\u003C\u002Fcode> — and the ten wasted requests become one, fired the moment the user actually stops typing. Which of your own event handlers is still firing ten times more than it needs to?\u003C\u002Fp>",{"id":430,"type":431},"b76","divider",{"id":433,"html":434,"type":124},"b77","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":436,"html":437,"type":124},"b78","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":439,"type":135,"items":440,"ordered":18},"b79",[441,442,443],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","Type \"javascript\" into an unthrottled search box and watch the network tab: an HTTP request for `j`, another for `ja`, another for `jav`, ten in total, nine of them thrown away before the response even lands. The server did ten times the work the feature needed, and the UI flickers with results for a query the user abandoned two keystrokes ago. The fix is two small, deceptively simple functions — debounce and throttle — and the part that trips people up isn't writing them, it's knowing exactly when each one fires.\n\n## What you'll learn\n\nBy the end of this guide you'll be able to:\n\n- Explain the precise difference between debounce and throttle, not just \"they both slow things down\"\n- Write a correct debounce and a correct throttle from scratch, including leading\u002Ftrailing edge behavior\n- Choose the right one for search inputs, scroll handlers, resize handlers, and button clicks\n- Avoid the memory leaks and stale-closure bugs both patterns cause in React and vanilla JS alike\n- Use a copy-paste utility with `cancel()` and `flush()` support\n\n**Who this is for:** you write JavaScript day to day, you've attached an event listener before, and you've either hand-rolled a `setTimeout` hack for this exact problem or reached for lodash without fully trusting what its defaults do.\n\n## Contents\n\n- [Why debounce and throttle exist](#why-debounce-and-throttle-exist)\n- [The mental model: a bouncer, not a filter](#the-mental-model-a-bouncer-not-a-filter)\n- [Stage 1: debounce from scratch](#stage-1-debounce-from-scratch)\n- [Stage 2: throttle from scratch](#stage-2-throttle-from-scratch)\n- [Stage 3: leading and trailing edges](#stage-3-leading-and-trailing-edges)\n- [Stage 4: cancel, flush, and cleanup](#stage-4-cancel-flush-and-cleanup)\n- [Stage 5: using them in React](#stage-5-using-them-in-react)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices](#best-practices-when-not-to-use-them)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n- [Key takeaways](#key-takeaways)\n\n## Why debounce and throttle exist\n\nHere's the naive version of a live search box — the one almost everyone writes first:\n\n```js\n\u002F\u002F the wrong way — fires a request on every single keystroke\nsearchInput.addEventListener(\"input\", (event) => {\n  fetchResults(event.target.value); \u002F\u002F one HTTP request per keystroke\n});\n```\n\nType a six-letter word at a normal pace and this fires six requests in well under a second, five of which are already obsolete by the time their responses arrive. Worse, network responses don't always resolve in the order they were sent — a slow response for `jav` can arrive *after* the fast response for `javascript`, and now the screen is showing stale results for a query the user already replaced. The bug isn't visible in a quick manual test because your laptop and the API are both fast; it shows up for a real user on a real network, and by then it looks like \"search is flaky\" rather than \"search fires way too often.\"\n\nThe same shape of problem hits scroll and resize handlers, just with a different failure mode. A `scroll` event can fire dozens of times per second. If the handler does anything nontrivial — reading `getBoundingClientRect()`, updating layout, running a chunk of business logic — the page starts dropping frames and scrolling turns jerky, even though nothing is technically \"broken.\"\n\nBoth problems come from the same root cause: **the event source fires far more often than the response actually needs to run.** Debounce and throttle are two different answers to \"how often is often enough,\" and they are not interchangeable.\n\n## The mental model: a bouncer, not a filter\n\n**The mental model:** neither function *filters* events — every event still reaches your wrapper and every event still runs a check. What changes is how often the check lets the real work through, and the two functions use opposite strategies for deciding that.\n\n- **Debounce says \"wait for quiet.\"** Every call resets a timer. The wrapped function only runs once the calls actually *stop* for the configured delay. Think of an elevator door: every time someone walks up, the door resets its close timer. It only closes once nobody has approached it for a few seconds.\n- **Throttle says \"at most once per interval.\"** It doesn't care whether calls are still coming in — it just refuses to let the wrapped function run again until a fixed amount of time has passed since the last time it ran. Think of a metronome, or a bouncer who lets one person through the door every two seconds regardless of how long the line is.\n\nThat single distinction — \"wait for silence\" versus \"space it out at a fixed rate\" — explains almost every behavior difference in the rest of this guide. Debounce is right when you only care about the *final* state (the finished search query). Throttle is right when you need *regular updates during* continuous activity (a scroll position that should keep updating while the user scrolls).\n\n## Stage 1: debounce from scratch\n\nThe smallest correct debounce is short — a closure holding one timer ID:\n\n```js\nfunction debounce(fn, delayMs) {\n  let timeoutId; \u002F\u002F lives across calls thanks to the closure\n  return function debounced(...args) {\n    clearTimeout(timeoutId); \u002F\u002F cancel whatever was pending\n    timeoutId = setTimeout(() => fn.apply(this, args), delayMs);\n  };\n}\n\nconst debouncedSearch = debounce((query) => fetchResults(query), 300);\nsearchInput.addEventListener(\"input\", (e) => debouncedSearch(e.target.value));\n```\n\n> **Key concept:** every call to `debounced` cancels the previous pending timer and starts a new one. `fn` only ever actually runs if 300ms pass with *no* new call in between — which is exactly \"wait for quiet,\" implemented as literally as possible.\n\nType \"js\" quickly and `debounced` runs twice (once per keystroke) but `fn` runs zero times until you stop — then it runs exactly once, 300ms after your last keystroke, with the final value of `query`. That's the whole mechanism. Everything else in this guide is a variation on this six-line function.\n\n## Stage 2: throttle from scratch\n\nThrottle needs to track *when it last ran*, not whether a timer is pending:\n\n```js\nfunction throttle(fn, intervalMs) {\n  let lastRun = 0; \u002F\u002F timestamp of the last time fn actually executed\n  return function throttled(...args) {\n    const now = Date.now();\n    if (now - lastRun >= intervalMs) {\n      lastRun = now;\n      fn.apply(this, args);\n    }\n  };\n}\n\nconst throttledOnScroll = throttle(() => updateScrollProgress(), 100);\nwindow.addEventListener(\"scroll\", throttledOnScroll);\n```\n\n> **Key concept:** this implementation runs `fn` immediately on the very first call (because `now - lastRun` starts effectively infinite), then ignores every call until `intervalMs` has elapsed, at which point the next call through gets to run. Calls that arrive during the \"cooldown\" are dropped entirely — not queued, not delayed, just discarded.\n\nThat last detail matters: with this specific implementation, if the burst of calls stops *during* a cooldown window, the very last call in the burst is simply lost — `fn` doesn't get one final run with the latest arguments. Stage 3 fixes that.\n\n## Stage 3: leading and trailing edges\n\n\"Leading edge\" means running on the *first* call in a burst; \"trailing edge\" means running once more after the burst ends, with the latest arguments. The debounce in Stage 1 is trailing-only. The throttle in Stage 2 is leading-only. A production-grade version usually supports both, because dropping the trailing call (throttle) or delaying every call including the first one (debounce) is sometimes the wrong tradeoff:\n\n```js\nfunction throttle(fn, intervalMs, { leading = true, trailing = true } = {}) {\n  let lastRun = 0;\n  let timeoutId = null;\n  let lastArgs = null;\n\n  return function throttled(...args) {\n    const now = Date.now();\n    const remaining = intervalMs - (now - lastRun);\n    lastArgs = args;\n\n    if (remaining \u003C= 0) {\n      if (leading || lastRun !== 0) {\n        lastRun = now;\n        fn.apply(this, args);\n      }\n    } else if (trailing && !timeoutId) {\n      timeoutId = setTimeout(() => {\n        timeoutId = null;\n        lastRun = Date.now();\n        fn.apply(this, lastArgs);\n      }, remaining);\n    }\n  };\n}\n```\n\n> **Key concept:** `leading` controls whether the very first call in a burst runs immediately; `trailing` controls whether one extra call fires after the burst goes quiet, using whatever arguments arrived last. lodash's `_.throttle` defaults to `{ leading: true, trailing: true }`, and its `_.debounce` defaults to `{ leading: false, trailing: true }` — which is exactly why debounce \"feels like\" it only fires at the end, while throttle \"feels like\" it fires immediately and then periodically.\n\n## Stage 4: cancel, flush, and cleanup\n\nA debounce or throttle you can't cancel is a liability the moment its owner disappears — a component unmounts, a modal closes, a request is superseded. Attach the controls directly to the returned function:\n\n```js\nfunction debounce(fn, delayMs) {\n  let timeoutId;\n  function debounced(...args) {\n    clearTimeout(timeoutId);\n    timeoutId = setTimeout(() => fn.apply(this, args), delayMs);\n  }\n  debounced.cancel = () => clearTimeout(timeoutId); \u002F\u002F drop the pending call\n  return debounced;\n}\n\nconst debouncedSave = debounce(saveDraft, 500);\ndebouncedSave(\"draft text\");\ndebouncedSave.cancel(); \u002F\u002F \"draft text\" will never be saved\n```\n\n`flush()` is the mirror image: run the pending call *right now* instead of waiting or dropping it — useful when the user explicitly submits a form while a debounced autosave is still pending, so the two writes don't race.\n\n## Stage 5: using them in React\n\nThe trap in React isn't the debounce function itself — it's *where* you create it. Creating a new debounced function on every render breaks the whole mechanism, because each render's closure has no memory of the previous render's timer:\n\n```jsx\n\u002F\u002F the wrong way — a brand-new debounce (and a brand-new timer) every render\nfunction SearchBox() {\n  const [query, setQuery] = useState(\"\");\n  const debouncedSearch = debounce((q) => fetchResults(q), 300); \u002F\u002F recreated every render\n\n  return (\n    \u003Cinput\n      value={query}\n      onChange={(e) => {\n        setQuery(e.target.value);\n        debouncedSearch(e.target.value); \u002F\u002F this instance's timer never gets to fire before a new one replaces it\n      }}\n    \u002F>\n  );\n}\n```\n\nCreate the debounced function once, with `useMemo` or `useRef`, and cancel it on unmount:\n\n```jsx\nfunction SearchBox() {\n  const [query, setQuery] = useState(\"\");\n  const debouncedSearch = useMemo(() => debounce((q) => fetchResults(q), 300), []);\n\n  useEffect(() => {\n    return () => debouncedSearch.cancel(); \u002F\u002F no fetch after this component is gone\n  }, [debouncedSearch]);\n\n  return (\n    \u003Cinput\n      value={query}\n      onChange={(e) => {\n        setQuery(e.target.value);\n        debouncedSearch(e.target.value);\n      }}\n    \u002F>\n  );\n}\n```\n\n> **Key concept:** the debounced function must outlive individual renders (created once, stored in a ref or memoized with an empty dependency array) and must be explicitly cancelled in a cleanup function, or its pending timer will call `fetchResults` on state that no longer exists.\n\n## Edge cases and gotchas\n\n- **Stale closures.** If `fn` inside a debounce\u002Fthrottle closes over a variable that changes between calls (a piece of state, a prop), the call that eventually fires can use an outdated value. `useCallback`\u002F`useRef` patterns exist specifically to solve this in React; in vanilla JS, pass the current value as an argument rather than relying on the closure.\n- **`this` binding.** The hand-written implementations above use `fn.apply(this, args)` so a debounced\u002Fthrottled *method* still sees the right `this`. If you strip that out, calling `debounce(obj.method, 300)()` silently breaks `this` inside `method`.\n- **Debouncing an `async` function doesn't cancel in-flight work.** Debounce delays *calling* the function; it does nothing about a `fetch` that's already in progress from a previous call. Pair debounce with an `AbortController` if a slow, superseded request could still resolve and overwrite a newer one.\n- **Race conditions between the trailing call and unmount.** A debounce's `setTimeout` keeps a reference to `fn` alive even after the component that created it is gone. Without `cancel()` on cleanup, the trailing call still fires and can throw (\"cannot update state on an unmounted component\") or write to a resource that no longer applies.\n- **Throttle intervals and animation.** A `scroll` or `mousemove` throttle set to a fixed millisecond interval can visibly stutter, because it's not synchronized with the browser's paint cycle. For anything visual, prefer `requestAnimationFrame`-based throttling: run at most once per frame instead of once per N milliseconds.\n- **Testing.** Both patterns depend on real time passing, which makes tests flaky if you `sleep`. Use fake timers (`jest.useFakeTimers()` \u002F `vi.useFakeTimers()`) and advance them explicitly (`jest.advanceTimersByTime(300)`) instead of waiting on the wall clock.\n- **Debounce delay versus perceived responsiveness.** A 300ms debounce feels instant to most users; anything above ~500ms on a search-as-you-type field starts to feel sluggish, because the user has already mentally \"sent\" the query.\n\n## Best practices (when (not) to use them)\n\n**Reach for debounce when** you only care about the value once activity settles: search-as-you-type, autosave, form validation that shouldn't run on every keystroke, resize-triggered layout recalculation where only the final size matters.\n\n**Reach for throttle when** you need periodic updates *during* continuous activity, not just at the end: scroll-position tracking, a progress indicator following `mousemove`, rate-limiting how often a \"user is typing\" indicator pings a server, infinite-scroll trigger checks.\n\n**Avoid both when** the action needs to feel instantaneous every single time — a button click, an \"add to cart,\" a keyboard shortcut. Delaying or dropping those erodes trust in the UI even if it's technically more \"efficient.\" If a click handler is slow, fix the handler; don't debounce the click.\n\n**Don't stack them by accident.** Wrapping an already-throttled handler in another library's debounce (or vice versa) is a common cause of \"my scroll handler feels randomly laggy\" bugs — pick one strategy per event source and be deliberate about the delay.\n\n## FAQ\n\n### What's the actual difference between debounce and throttle?\n\nDebounce waits for a pause in activity and then runs once; throttle runs at a fixed maximum rate regardless of whether activity is still ongoing. Debounce answers \"what's the final state?\"; throttle answers \"give me periodic updates while this keeps happening.\"\n\n### Does lodash's `_.debounce` behave differently from a hand-rolled one?\n\nFunctionally, a correct hand-rolled trailing-edge debounce matches `_.debounce`'s default behavior (`leading: false, trailing: true`). lodash additionally ships `cancel()`, `flush()`, and a `maxWait` option (a ceiling on how long calls can be delayed even under continuous activity) — genuinely useful extras, not a different core algorithm.\n\n### Can I debounce an async function?\n\nYes, but debounce only controls *when the call happens* — it has no knowledge of what the async function does afterward. If an earlier (superseded) call's promise resolves after a later one, you can still get out-of-order results unless you also track \"is this the latest call\" or cancel the earlier request with an `AbortController`.\n\n### Should I use `requestAnimationFrame` instead of throttle for scroll or resize?\n\nFor anything that updates visuals (position, size, opacity), yes — `requestAnimationFrame` throttling caps the work at once per paint, which is both smoother and never more work than the browser can actually display. Millisecond-based throttling is still the right tool for non-visual rate-limiting, like capping how often you ping an analytics endpoint.\n\n### How do I cancel a debounced or throttled call?\n\nAttach a `.cancel()` method to the returned function (Stage 4) and call it — in `setTimeout`'s case that's a `clearTimeout`; for lodash, `_.debounce` and `_.throttle` both return functions with a built-in `.cancel()`.\n\n## Cheat sheet\n\n| Task | Code | Notes |\n| --- | --- | --- |\n| Debounce (trailing only) | `debounce(fn, 300)` | Runs once, 300ms after calls stop. Best for search\u002Fautosave. |\n| Throttle (leading + trailing) | `throttle(fn, 100, { leading: true, trailing: true })` | Runs immediately, then at most every 100ms, plus once more after the burst ends. |\n| Cancel a pending call | `debounced.cancel()` | Clears the timer; the delayed call never runs. |\n| Flush a pending call now | `debounced.flush()` | Runs the pending call immediately instead of waiting. |\n| Visual\u002Fanimation rate-limit | `requestAnimationFrame` loop | Caps work to once per paint; smoother than a fixed-ms throttle for scroll\u002Fresize. |\n| React: create once | `useMemo(() => debounce(fn, ms), [])` | Never recreate inside the render body. |\n| React: cleanup | `useEffect(() => () => debounced.cancel(), [])` | Prevents calls firing after unmount. |\n\n```js\n\u002F\u002F the whole pattern, copy-paste ready\nfunction debounce(fn, delayMs) {\n  let timeoutId;\n  function debounced(...args) {\n    clearTimeout(timeoutId);\n    timeoutId = setTimeout(() => fn.apply(this, args), delayMs);\n  }\n  debounced.cancel = () => clearTimeout(timeoutId);\n  return debounced;\n}\n\nfunction throttle(fn, intervalMs, { leading = true, trailing = true } = {}) {\n  let lastRun = 0;\n  let timeoutId = null;\n  let lastArgs = null;\n\n  function throttled(...args) {\n    const now = Date.now();\n    const remaining = intervalMs - (now - lastRun);\n    lastArgs = args;\n\n    if (remaining \u003C= 0) {\n      if (leading || lastRun !== 0) {\n        lastRun = now;\n        fn.apply(this, args);\n      }\n    } else if (trailing && !timeoutId) {\n      timeoutId = setTimeout(() => {\n        timeoutId = null;\n        lastRun = Date.now();\n        fn.apply(this, lastArgs);\n      }, remaining);\n    }\n  }\n  throttled.cancel = () => {\n    clearTimeout(timeoutId);\n    timeoutId = null;\n  };\n  return throttled;\n}\n```\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-debounce-and-throttle\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Key takeaways\n\n- Debounce waits for quiet and runs once at the end; throttle runs on a fixed schedule no matter how long the activity continues.\n- Every event still reaches the wrapper — what changes is how often the *real* work behind it is allowed to run.\n- Use debounce for \"what's the final value\" (search, autosave); use throttle (or `requestAnimationFrame`) for \"keep me updated while this continues\" (scroll, resize, drag).\n- Both need `cancel()` wired into cleanup — an uncancelled debounce or throttle is a call waiting to fire on a component that no longer exists.\n- Create the wrapped function once, not on every render.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-debounce-and-throttle\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\nThat search box from the opening paragraph needs exactly one line changed — wrap the handler in `debounce(fetchResults, 300)` — and the ten wasted requests become one, fired the moment the user actually stops typing. Which of your own event handlers is still firing ten times more than it needs to?\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":95,"canonical":446,"description":97},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fweekly-debounce-and-throttle","019ff73a-fb5b-73ce-b725-2e18132de784",{"name":449,"part":80,"total":80,"items":450},"Deep Dive",[451,456,460,464,468,472],{"slug":452,"title":453,"publishedAt":454,"readingMinutes":455},"weekly-javascript-event-loop","JavaScript Event Loop Explained: The Complete Guide","2026-07-13T09:36:33.464Z",13,{"slug":457,"title":458,"publishedAt":459,"readingMinutes":64},"weekly-typescript-generics","TypeScript Generics: The Complete Guide (with Cheat Sheet)","2026-07-20T09:23:23.621Z",{"slug":461,"title":462,"publishedAt":463,"readingMinutes":99},"weekly-cors-explained","CORS Explained: The Complete Guide (with Cheat Sheet)","2026-08-03T09:54:26.268Z",{"slug":465,"title":466,"publishedAt":467,"readingMinutes":64},"weekly-javascript-proxy-reflect","JavaScript Proxy and Reflect: The Complete Guide","2026-08-24T07:06:37.948Z",{"slug":469,"title":470,"publishedAt":471,"readingMinutes":99},"weekly-javascript-closures","JavaScript Closures: The Complete Guide (with Cheat Sheet)","2026-08-25T08:53:48.904Z",{"slug":94,"title":95,"publishedAt":100,"readingMinutes":99},{"id":474,"locked":18},"019ff73a-fb87-74e8-be02-4aaa15cc945e",[476],{"id":477,"slug":94,"title":478,"_count":479},"019ff73a-fb9f-7786-9533-1f17ba3783af","Debounce and Throttle in JavaScript",{"questions":480},8,[482],{"locale":13,"slug":94},{"id":477,"slug":94,"title":478,"_count":484,"questionCount":480},{"questions":480},{"items":486,"meta":611},[487,499,519,541,559,577,596],{"id":93,"slug":94,"title":95,"subtitle":96,"excerpt":97,"coverUrl":98,"locale":13,"readingMinutes":99,"publishedAt":100,"viewCount":488,"likeCount":19,"commentCount":19,"author":489,"vertical":490,"topic":491,"tags":492,"_count":497,"playground":498,"hasQuiz":17,"hasPlayground":17},28,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":54,"name":109},[493,494,495,496],{"slug":54,"name":55,"color":96},{"slug":46,"name":47,"color":96},{"slug":62,"name":63,"color":96},{"slug":70,"name":71,"color":96},{"assessments":116},{"slug":94},{"id":500,"slug":501,"title":502,"subtitle":96,"excerpt":503,"coverUrl":504,"locale":13,"readingMinutes":80,"publishedAt":505,"viewCount":506,"likeCount":19,"commentCount":19,"author":507,"vertical":508,"topic":509,"tags":510,"_count":517,"playground":518,"hasQuiz":17,"hasPlayground":17},"01a03cdb-3743-73ef-ab9d-21f03158ca35","server-sent-events-eventsource-live-updates","You Don't Need a WebSocket for That Live Feed","A live notification badge, a progress bar, a dashboard counter — you reached for socket.io and started hand-rolling reconnect logic. The browser already ships a simpler API that does it for you.","\u002Fmedia\u002Fcovers\u002Fserver-sent-events-eventsource-live-updates.png","2026-08-31T13:11:42.385Z",9,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":54,"name":109},[511,512,513,514],{"slug":54,"name":55,"color":96},{"slug":46,"name":47,"color":96},{"slug":62,"name":63,"color":96},{"slug":515,"name":516,"color":96},"apis","Apis",{"assessments":116},{"slug":501},{"id":520,"slug":521,"title":522,"subtitle":96,"excerpt":523,"coverUrl":524,"locale":13,"readingMinutes":99,"publishedAt":525,"viewCount":526,"likeCount":19,"commentCount":19,"author":527,"vertical":528,"topic":529,"tags":532,"_count":539,"playground":540,"hasQuiz":17,"hasPlayground":17},"01a04446-3f08-701c-8cdc-e5ab5e87086e","nuxt-weekly-cross-request-state-leak","Nuxt useState vs ref(): Why Server State Leaks Across Users","A module-scope ref() in Nuxt is shared by every request that hits your server. Learn why useState isolates state per user, and how to fix the leak.","\u002Fmedia\u002Fcovers\u002Fnuxt-weekly-cross-request-state-leak.png","2026-08-30T11:37:11.335Z",86,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":530,"name":531},"nuxt","Nuxt",[533,534,537,538],{"slug":530,"name":531,"color":96},{"slug":535,"name":536,"color":96},"ssr","SSR",{"slug":54,"name":55,"color":96},{"slug":62,"name":63,"color":96},{"assessments":116},{"slug":521},{"id":542,"slug":543,"title":544,"subtitle":96,"excerpt":545,"coverUrl":546,"locale":13,"readingMinutes":455,"publishedAt":547,"viewCount":548,"likeCount":19,"commentCount":19,"author":549,"vertical":550,"topic":551,"tags":552,"_count":557,"playground":558,"hasQuiz":17,"hasPlayground":17},"01a04446-373a-775b-8aaf-b72c8952123d","react-weekly-rerender-vs-remount","React Re-render vs Remount: What Actually Triggers Each","A practical guide to React re-render vs remount: what type, position, and key decide, why state resets unexpectedly, and how to force a remount.","\u002Fmedia\u002Fcovers\u002Freact-weekly-rerender-vs-remount.png","2026-08-29T12:32:41.200Z",143,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":74,"name":75},[553,554,555,556],{"slug":74,"name":75,"color":96},{"slug":54,"name":55,"color":96},{"slug":62,"name":63,"color":96},{"slug":46,"name":47,"color":96},{"assessments":116},{"slug":543},{"id":560,"slug":561,"title":562,"subtitle":96,"excerpt":563,"coverUrl":564,"locale":13,"readingMinutes":84,"publishedAt":565,"viewCount":566,"likeCount":19,"commentCount":19,"author":567,"vertical":568,"topic":569,"tags":570,"_count":575,"playground":576,"hasQuiz":17,"hasPlayground":17},"01a02839-480f-7428-914a-2c0442d00855","intl-segmenter-grapheme-word-sentence","You're truncating bios with `.slice()`. `Intl.Segmenter` knows where the emoji actually end.","A 30-character bio limit that cuts off mid-emoji isn't a rendering bug — it's .length counting UTF-16 code units instead of what's on screen. Intl.Segmenter counts graphemes, words, and sentences the way a reader actually sees them, and every major browser supports it now.","\u002Fmedia\u002Fcovers\u002Fintl-segmenter-grapheme-word-sentence.png","2026-08-27T17:30:57.707Z",199,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":54,"name":109},[571,572,573,574],{"slug":54,"name":55,"color":96},{"slug":46,"name":47,"color":96},{"slug":50,"name":51,"color":96},{"slug":62,"name":63,"color":96},{"assessments":116},{"slug":561},{"id":578,"slug":579,"title":580,"subtitle":96,"excerpt":581,"coverUrl":582,"locale":13,"readingMinutes":84,"publishedAt":583,"viewCount":584,"likeCount":19,"commentCount":19,"author":585,"vertical":586,"topic":587,"tags":589,"_count":594,"playground":595,"hasQuiz":17,"hasPlayground":17},"01a02319-4485-7310-b494-dc94bdbfb49c","css-field-sizing-auto-grow-textarea","Delete Your Auto-Resize Textarea JS. CSS Does It Now.","The scrollHeight hack everyone copy-pastes for auto-growing textareas has a one-line CSS replacement — plus the one gotcha that bites if you skip max-height.","\u002Fmedia\u002Fcovers\u002Fcss-field-sizing-auto-grow-textarea.png","2026-08-26T06:56:38.974Z",216,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":58,"name":588},"CSS",[590,591,592,593],{"slug":58,"name":59,"color":96},{"slug":46,"name":47,"color":96},{"slug":50,"name":51,"color":96},{"slug":54,"name":55,"color":96},{"assessments":116},{"slug":579},{"id":597,"slug":469,"title":470,"subtitle":96,"excerpt":598,"coverUrl":599,"locale":13,"readingMinutes":99,"publishedAt":471,"viewCount":600,"likeCount":19,"commentCount":19,"author":601,"vertical":602,"topic":603,"tags":604,"_count":609,"playground":610,"hasQuiz":17,"hasPlayground":17},"019fe661-0c1b-7408-8419-531dae95bdf1","Learn how JavaScript closures really work — lexical scope, private state, memoization, and the classic loop bug — with worked examples and a cheat sheet.","\u002Fmedia\u002Fcovers\u002Fweekly-javascript-closures.png",281,{"id":103,"name":104,"username":105,"avatarUrl":96,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":54,"name":109},[605,606,607,608],{"slug":54,"name":55,"color":96},{"slug":46,"name":47,"color":96},{"slug":62,"name":63,"color":96},{"slug":86,"name":87,"color":96},{"assessments":116},{"slug":469},{"page":116,"perPage":76,"total":56,"totalPages":506},"\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F the wrong way — fires a request on every single keystroke\n\u003C\u002Fspan>searchInput\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"input\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-func\">fetchResults\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>event\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F one HTTP request per keystroke\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debounce\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>fn\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> delayMs\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> timeoutId; \u003Cspan class=\"shj-cmnt\">\u002F\u002F lives across calls thanks to the closure\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debounced\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-func\">clearTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>timeoutId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F cancel whatever was pending\n\u003C\u002Fspan>    timeoutId \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> fn\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">apply\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> delayMs\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> debouncedSearch \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debounce\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>query\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetchResults\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>query\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">300\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nsearchInput\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"input\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debouncedSearch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">throttle\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>fn\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> intervalMs\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> lastRun \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F timestamp of the last time fn actually executed\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">throttled\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> now \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Date\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">now\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>now \u003Cspan class=\"shj-oper\">-\u003C\u002Fspan> lastRun \u003Cspan class=\"shj-oper\">&gt;=\u003C\u002Fspan> intervalMs\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      lastRun \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> now;\n      fn\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">apply\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> throttledOnScroll \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">throttle\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">updateScrollProgress\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">100\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\nwindow\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">addEventListener\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"scroll\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> throttledOnScroll\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003Cdiv>21\u003C\u002Fdiv>\u003Cdiv>22\u003C\u002Fdiv>\u003Cdiv>23\u003C\u002Fdiv>\u003Cdiv>24\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">throttle\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>fn\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> intervalMs\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> leading \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> trailing \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> lastRun \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> timeoutId \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> lastArgs \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">throttled\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> now \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Date\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">now\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> remaining \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> intervalMs \u003Cspan class=\"shj-oper\">-\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>now \u003Cspan class=\"shj-oper\">-\u003C\u002Fspan> lastRun\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    lastArgs \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> args;\n\n    \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>remaining \u003Cspan class=\"shj-oper\">&lt;=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>leading \u003Cspan class=\"shj-oper\">||\u003C\u002Fspan> lastRun \u003Cspan class=\"shj-oper\">!==\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        lastRun \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> now;\n        fn\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">apply\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">else\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>trailing \u003Cspan class=\"shj-oper\">&#38;&#38;\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>timeoutId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      timeoutId \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        timeoutId \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>;\n        lastRun \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Date\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">now\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n        fn\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">apply\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> lastArgs\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> remaining\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debounce\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>fn\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> delayMs\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> timeoutId;\n  \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debounced\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-func\">clearTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>timeoutId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    timeoutId \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> fn\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">apply\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> delayMs\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  debounced\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">cancel\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">clearTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>timeoutId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F drop the pending call\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> debounced;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> debouncedSave \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debounce\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>saveDraft\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">500\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-func\">debouncedSave\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"draft text\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\ndebouncedSave\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">cancel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F \"draft text\" will never be saved\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-jsx shj-multiline\" data-lang=\"jsx\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F the wrong way — a brand-new debounce (and a brand-new timer) every render\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">SearchBox\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> setQuery\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">useState\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> debouncedSearch \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debounce\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetchResults\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">300\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F recreated every render\n\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan>\n      \u003Cspan class=\"shj-class\">value\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\n      \u003Cspan class=\"shj-class\">onChange\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        \u003Cspan class=\"shj-func\">setQuery\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n        \u003Cspan class=\"shj-func\">debouncedSearch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F this instance's timer never gets to fire before a new one replaces it\n\u003C\u002Fspan>      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\n    \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-jsx shj-multiline\" data-lang=\"jsx\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-class\">SearchBox\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> setQuery\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">useState\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> debouncedSearch \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">useMemo\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debounce\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetchResults\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>q\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">300\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-func\">useEffect\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> debouncedSearch\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">cancel\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F no fetch after this component is gone\n\u003C\u002Fspan>  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>debouncedSearch\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n    \u003Cspan class=\"shj-var\">\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-var\">input\u003C\u002Fspan>\n      \u003Cspan class=\"shj-class\">value\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>query\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\n      \u003Cspan class=\"shj-class\">onChange\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">={\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        \u003Cspan class=\"shj-func\">setQuery\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n        \u003Cspan class=\"shj-func\">debouncedSearch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>target\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">}\u003C\u002Fspan>\n    \u003Cspan class=\"shj-oper\">\u002F&gt;\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003Cdiv>20\u003C\u002Fdiv>\u003Cdiv>21\u003C\u002Fdiv>\u003Cdiv>22\u003C\u002Fdiv>\u003Cdiv>23\u003C\u002Fdiv>\u003Cdiv>24\u003C\u002Fdiv>\u003Cdiv>25\u003C\u002Fdiv>\u003Cdiv>26\u003C\u002Fdiv>\u003Cdiv>27\u003C\u002Fdiv>\u003Cdiv>28\u003C\u002Fdiv>\u003Cdiv>29\u003C\u002Fdiv>\u003Cdiv>30\u003C\u002Fdiv>\u003Cdiv>31\u003C\u002Fdiv>\u003Cdiv>32\u003C\u002Fdiv>\u003Cdiv>33\u003C\u002Fdiv>\u003Cdiv>34\u003C\u002Fdiv>\u003Cdiv>35\u003C\u002Fdiv>\u003Cdiv>36\u003C\u002Fdiv>\u003Cdiv>37\u003C\u002Fdiv>\u003Cdiv>38\u003C\u002Fdiv>\u003Cdiv>39\u003C\u002Fdiv>\u003Cdiv>40\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F the whole pattern, copy-paste ready\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debounce\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>fn\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> delayMs\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> timeoutId;\n  \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">debounced\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-func\">clearTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>timeoutId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    timeoutId \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> fn\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">apply\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> delayMs\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  debounced\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">cancel\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">clearTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>timeoutId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> debounced;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">throttle\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>fn\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> intervalMs\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> leading \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> trailing \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> lastRun \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> timeoutId \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> lastArgs \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">throttled\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">...\u003C\u002Fspan>args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> now \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Date\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">now\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> remaining \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> intervalMs \u003Cspan class=\"shj-oper\">-\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>now \u003Cspan class=\"shj-oper\">-\u003C\u002Fspan> lastRun\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    lastArgs \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> args;\n\n    \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>remaining \u003Cspan class=\"shj-oper\">&lt;=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>leading \u003Cspan class=\"shj-oper\">||\u003C\u002Fspan> lastRun \u003Cspan class=\"shj-oper\">!==\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        lastRun \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> now;\n        fn\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">apply\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> args\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">else\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>trailing \u003Cspan class=\"shj-oper\">&#38;&#38;\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>timeoutId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      timeoutId \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        timeoutId \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>;\n        lastRun \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Date\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">now\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n        fn\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">apply\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> lastArgs\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> remaining\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  throttled\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">cancel\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-func\">clearTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>timeoutId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n    timeoutId \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> throttled;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":621},[]]