Search boxes that fire a request on every keystroke and scroll handlers that run 100 times a second are classic performance bugs. Two tiny functions fix them.

Debounce: wait until the user stops

function debounce(fn, delay = 300) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } 

Each call resets the timer. fn only runs once calls stop for delay ms.

Throttle: run at most once per interval

function throttle(fn, interval = 200) { let last = 0; let trailing; return function (...args) { const now = Date.now(); const remaining = interval - (now - last); clearTimeout(trailing); if (remaining <= 0) { last = now; fn.apply(this, args); } else { // make sure the final call isn't lost trailing = setTimeout(() => { last = Date.now(); fn.apply(this, args); }, remaining); } }; } 

Try it in a page

<input id="search" placeholder="Type..."> <p id="out"></p> <p id="scroll"></p> <div style="height:3000px"></div> <script> /* paste debounce and throttle here */ const out = document.getElementById("out"); document.getElementById("search").addEventListener( "input", debounce(e => { out.textContent = "Searching for: " + e.target.value; }, 400) ); const s = document.getElementById("scroll"); window.addEventListener( "scroll", throttle(() => { s.textContent = "scrollY = " + Math.round(scrollY); }, 250) ); </script> 

Test it in Node

const log = debounce(x => console.log("debounced:", x), 100); log(1); log(2); log(3); // prints only "debounced: 3" const tick = throttle(x => console.log("throttled:", x), 100); let i = 0; const id = setInterval(() => { tick(++i); if (i === 10) clearInterval(id); }, 30); // prints roughly every 100ms, plus the final value 

Which one?

Situation Use
Search-as-you-type, autosave, window resize end debounce
Scroll position, mouse move, infinite scroll check throttle

Rule of thumb: debounce cares about the last event, throttle cares about a steady rhythm.