
Introducing Debouncing & Throttling
Moamen Sherif
-December 13, 2025
8 minutes read
When events fire rapidly (typing, scroll, resize), it’s wasteful to run heavy logic on every single event; Debouncing waits for a pause, while Throttling runs at fixed intervals to control frequency.
What is Debouncing?
Debouncing delays running a function until a set time has passed without any new events, which is ideal for search inputs, validation, and auto‑save after the user pauses.
- In plain terms: “wait for quiet, then run once,” which reduces noisy, repeated work from rapid event bursts.
- Popular libraries like Lodash provide a robust
_.debounce(func, wait, options)utility out of the box.
What is Throttling?
Throttling guarantees a function runs at most once per interval, even if events keep firing, which is great for scroll, mousemove, and resize performance. [16]
- In plain terms: “run at a steady pace,” smoothing out frequent events to protect UI performance.
- Lodash also ships
_.throttle(func, wait, options)with flexible leading/trailing options.
Vanilla JS — Debounce Implementation
A small, reusable debounce helper using setTimeout and clearTimeout.
// debounce.js export function debounce(fn, wait = 300) { let timeout; return function debounced(...args) { const ctx = this; clearTimeout(timeout); timeout = setTimeout(() => fn.apply(ctx, args), wait); }; }
Use it for search input to avoid firing a request per keystroke.
// search-input.js import { debounce } from './debounce.js'; const input = document.querySelector('#search'); function fetchSuggestions(q) { // call API with q console.log('Fetching:', q); } const onInput = debounce((e) => fetchSuggestions(e.target.value), 400); input.addEventListener('input', onInput);
Vanilla JS — Throttle Implementation
A simple throttle that runs at most once each interval; ideal for scroll/resize handlers.
// throttle.js export function throttle(fn, wait = 300, { leading = true, trailing = true } = {}) { let lastCall = 0; let timeout = null; let lastArgs; let lastThis; const invoke = () => { lastCall = Date.now(); timeout = null; fn.apply(lastThis, lastArgs); }; return function throttled(...args) { const now = Date.now(); const remaining = wait - (now - lastCall); lastArgs = args; lastThis = this; if (remaining <= 0) { if (leading || lastCall !== 0) invoke(); else lastCall = now; // handle leading: false first call } else if (trailing && !timeout) { timeout = setTimeout(invoke, remaining); } }; }
Limit scroll updates to once every X ms to keep the UI smooth and CPU cool. [16][6]
// scroll-handler.js import { throttle } from './throttle.js'; function onScroll() { // inexpensive, batched updates console.log('Scroll at:', window.scrollY); } window.addEventListener('scroll', throttle(onScroll, 200));
React — Using Lodash Debounce (with stable refs)
Memoize the debounced function so it isn’t recreated on every render; this avoids lost timers and ensures correct behavior.
// SearchBox.jsx import { useMemo, useState } from 'react'; import debounce from 'lodash.debounce'; export default function SearchBox() { const [query, setQuery] = useState(''); const debouncedFetch = useMemo( () => debounce((q) => { // fetch suggestions here console.log('Fetching:', q); }, 400), [] ); return ( <input value={query} onChange={(e) => { const q = e.target.value; setQuery(q); debouncedFetch(q); }} placeholder="Type to search..." /> ); }
If a callback must capture changing dependencies, wrap them or cancel/recreate carefully; many issues come from not memoizing the debounced function in React.
React — Using Lodash Throttle (scroll/resize/move)
Throttle heavy handlers to keep interactions responsive.
// ScrollIndicator.jsx import { useEffect, useMemo, useState } from 'react'; import throttle from 'lodash.throttle'; export default function ScrollIndicator() { const [pos, setPos] = useState(0); const onScrollThrottled = useMemo( () => throttle(() => setPos(window.scrollY), 200), [] ); useEffect(() => { window.addEventListener('scroll', onScrollThrottled); return () => { window.removeEventListener('scroll', onScrollThrottled); onScrollThrottled.cancel?.(); }; }, [onScrollThrottled]); return <div>Scroll Y: {pos}</div>; }
Which one to use?
- Use Debouncing when waiting for the user to pause (search boxes, input validation, auto‑save) to reduce noisy, redundant calls.
- Use Throttling when handling rapid continuous events (scroll, resize, mousemove) to execute at a steady cadence.
Quick notes
- Lodash
_.debounceand_.throttleare well‑tested utilities; prefer them over ad‑hoc code for production. - Both techniques are framework‑agnostic; the concepts are the same in Vanilla JS, React, Vue, or Angular.
