input, scroll, resize and pointermove fire faster than a search request should run. Two wrappers limit the rate:
Debounce runs once after calls stop for a quiet period: search-as-you-type, autosave, validation.
Throttle runs at most once per interval: scroll position, drag feedback. For painting, requestAnimationFrame() (requestAnimationFrame) throttles to the display.
const debounce = (fn, wait) => { // run once calls stop for `wait` ms
let timer;
return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), wait); };
};
const throttle = (fn, wait) => { // at most once per `wait` ms, first and last call included
let last = -Infinity, timer;
return (...args) => {
const run = () => { last = performance.now(); fn(...args); };
clearTimeout(timer);
const left = last + wait - performance.now();
if (left <= 0) run(); else timer = setTimeout(run, left);
};
};
const t0 = performance.now(), log = { debounced: [], throttled: [] };
const rec = (name) => () => log[name].push(Math.round(performance.now() - t0));
const d = debounce(rec('debounced'), 300), t = throttle(rec('throttled'), 300);
for (let i = 0; i < 20; i++) { d(); t(); await new Promise((r) => setTimeout(r, 50)); }
setTimeout(() => console.log(JSON.stringify(log)), 400);{"debounced":[1394],"throttled":[0,300,613,917,1230]}Twenty calls became one and five. Lodash 61,278 's debounce adds leading, trailing and maxWait options; a debounced fetch() should also abort its predecessor (Practical Asynchrony).
Virtualized lists
Throttling cannot save 100,000 rows: the nodes themselves are the cost. A virtualized (windowed) list puts one empty spacer total × rowHeight pixels tall in a scroll box, and on each scroll renders only the visible rows plus a few of overscan, absolutely positioned at index × rowHeight. A 240-pixel box holds about 16 rows wherever you scroll. TanStack Virtual 79,069 (https://github.com/TanStack/virtual 7,123 ) (MIT, npm 2,036 install @tanstack/virtual-core, adapters for React 7,897 , Vue 5,482 , Solid 269,718 , Svelte 36,701 and Angular 42,088 ) adds measured row heights and grids. Windowing breaks find-in-page and hides off-screen rows from screen readers, so for a few thousand rows try content-visibility: auto (Visibility) first.