Using Lodash debounce to limit rapid UI event handlers
Learn how to use Lodash's debounce to limit costly UI event handlers to a set interval, with step‑by‑step setup, verification, and recovery tips.
03 Mar 2026, 07:36 UTC

Desired outcome
When a user resizes a window, types in an input, or scrolls a page, the associated event can fire dozens of times per second. If the event handler performs expensive work (layout calculations, network requests, heavy DOM updates), the browser can become unresponsive. The goal is to ensure the handler runs at most once every wait milliseconds, with a final call after the event stream stops when trailing:true is set.
Prerequisites
- Lodash version 4.17.21 or later installed via
npm i lodashoryarn add lodash. - A JavaScript project that can manipulate the DOM (e.g., a plain HTML page, a React component, or a Vue template).
- Basic familiarity with importing modules and attaching event listeners.
Procedure
- Import the debounce utility
If you are using an ES‑module build system:
import { debounce } from 'lodash';For a CommonJS environment:
const { debounce } = require('lodash'); - Define the costly handler
This is the function you want to throttle. Keep it pure or side‑effect‑limited for easier testing.
function expensiveUpdate() { // Example: recalculate layout, fetch data, or update a canvas console.log('Handler executed at', new Date().toISOString()); } - Create a debounced version
Wrap the handler with
_.debounce. Setleading:falseif you only want the trailing call after the pause, andtrailing:trueto guarantee a final execution when the events stop.const debouncedUpdate = debounce(expensiveUpdate, 250, { leading: false, trailing: true }); - Attach the debounced function to the event target
Replace the original handler with the debounced one. Keep a reference to the debounced function so you can cancel it later if needed.
window.addEventListener('resize', debouncedUpdate); - Optional: clean up on component unload or page teardown
Calling
cancelclears any pending timer, preventing a stray trailing call after the component is removed.// Example cleanup in a React useEffect return function return () => { debouncedUpdate.cancel(); };
Expected checks
- Open the page in a browser with the console open.
- Rapidly resize the window (or trigger the chosen event) for several seconds.
- Observe the console logs: timestamps should be spaced at least
waitmilliseconds apart. - After you stop resizing, wait a little longer than
wait(e.g., 300 ms ifwaitis 250) and verify that one final log appears (the trailing call). - To test cleanup, invoke
debouncedUpdate.cancel()while resizing is ongoing and confirm that no further logs appear after cancellation.
Recovery options
- If the debounced function fires too often or not at all, first check that you are holding a reference to the same debounced function you attached; creating a new debounce on each render will break the timing.
- Adjust the
waitvalue: increase it to reduce frequency, decrease it to improve responsiveness. - If you need a guaranteed maximum rate regardless of how long the event stream lasts, consider
_.throttleinstead of debounce. - Should the handler itself cause layout thrash, move heavy work out of the handler (e.g., use requestAnimationFrame or off‑screen canvas) and keep the debounced callback lightweight.
Limitations and practical verification
Debounce only controls how often the wrapped function is invoked; it does not throttle the underlying event frequency. If the handler performs synchronous layout reads/writes, the browser may still drop frames. To verify that the debounce is working as intended, you can instrument the handler with a performance marker:
function expensiveUpdate() { const start = performance.now(); // … work … console.log('Duration:', performance.now() - start, 'ms'); }Then, in the console, ensure that the reported duration stays below your frame budget (typically < 16 ms for 60 fps) and that the intervals between calls respect the
waitvalue.Diagram
The following labels illustrate the flow of a debounced resize handler:
- Window resize event
- Debounced handler (timer)
- Trailing call after pause
- Cancel clears pending timer
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.