Skip to main content

debounce()

debounce<Args, Context>(func, wait, immediate?): (this, ...args) => void & object

Creates a debounced function that delays execution until after a wait period.


Type Parameters​

Args: Args extends unknown[]​

The argument types of the function.

Context: Context = unknown​

The type of this context.


Parameters​

func: (this, ...args) => void​

The function to debounce.

wait: number​

The number of milliseconds to delay.

immediate?: boolean = false​

If true, trigger on leading edge instead of trailing edge.


Returns: (this, ...args) => void & object​

The debounced function with cancel() and flush() methods.


Throws​

RangeError If wait is negative or not finite.


Since​

2.0.0


Performance​

Clears previous timeout on each call to prevent multiple pending executions.


Also known as​

debounce (Lodash, es-toolkit, Remeda, Radashi, Modern Dash) · ❌ (Ramda, Effect, Antfu)


Example​

// Trailing edge (default)
const search = debounce((query: string) => {
console.log(`Searching: ${query}`);
}, 300);

search('abc'); // After 300ms: "Searching: abc"

// Leading edge
const save = debounce(() => console.log('Saved'), 1000, true);
save(); // Immediately: "Saved"

// Cancel pending
search.cancel();

// Force immediate execution
search('urgent');
search.flush(); // Immediately: "Searching: urgent"

How it works?​

Debounce delays execution until the user stops calling for a specified duration. Each call resets the timer β€” only the last call triggers the function.

Debounce vs Throttle​

Debounce waits for silence β€” resets on every call. Throttle enforces rhythm β€” fires at fixed intervals.

Debounce vs Throttle comparison

DebounceThrottle
FiresOnce, after silencePeriodically
Best forSearch inputScroll events

Use Cases​

Optimize search input performance πŸ“Œβ€‹

Delay search execution until user stops typing to reduce API calls and improve performance. Essential for search interfaces and autocomplete functionality.

const search = debounce((query) => {
api.search(query);
}, 300);

// User types "hello"... only one API call after 300ms
input.addEventListener("input", (e) => search((e.target as HTMLInputElement).value));

Delay API calls until idle πŸ“Œβ€‹

Delay API requests until user stops interacting to prevent server overload. Critical for API optimization and reducing unnecessary network traffic.

const saveData = debounce((data) => {
api.save(data);
}, 1000);

// Save only after user stops editing for 1 second
editor.on("change", (data) => saveData(data));

Prevent rapid button clicks​

Debounce button clicks to prevent accidental multiple submissions. Useful for form submissions and user interface interactions.

const submitForm = debounce(() => {
form.submit();
}, 500, true); // Immediate = true

// Only first click triggers action
button.addEventListener("click", submitForm);

Rate-limit WebSocket messages in real-time apps​

Debounce high-frequency WebSocket messages to prevent UI overload. Critical for trading platforms, live dashboards, and real-time collaboration tools.

// Debounce price updates from trading WebSocket
const handlePriceUpdate = debounce((priceData) => {
updateTradingChart(priceData);
recalculatePortfolioValue(priceData);
}, 100);

websocket.on("price_update", (data) => {
handlePriceUpdate(data);
});

// For live chat typing indicators
const sendTypingIndicator = debounce(() => {
socket.emit("user_typing", { roomId, userId });
}, 500);

messageInput.addEventListener("input", sendTypingIndicator);

Auto-save documents while editing​

Save document or form content automatically after the user stops typing. Essential for rich text editors, note-taking apps, and any "Google Docs-like" experience.

const autoSave = debounce(async (content: string) => {
await api.saveDraft({ content, updatedAt: Date.now() });
showSaveIndicator("Saved");
}, 2000);

editor.on("change", (content) => {
showSaveIndicator("Saving...");
autoSave(content);
});

Debounce filter panel changes​

Wait for the user to finish adjusting filters before fetching results. Essential for faceted search with multiple filter controls (sliders, checkboxes).

const applyFilters = debounce(async (filters: FilterState) => {
const results = await api.searchProducts(filters);
renderProductGrid(results);
updateFilterCounts(results.facets);
}, 400);

priceSlider.addEventListener("input", (e) => {
applyFilters({ ...currentFilters, priceRange: parseRange(e) });
});

categoryCheckbox.addEventListener("change", () => {
applyFilters({ ...currentFilters, categories: getCheckedCategories() });
});

Delay window resize recalculations​

Recalculate chart dimensions or complex layouts only after resizing stops. Critical for chart libraries and responsive canvas elements.

const recalcLayout = debounce(() => {
const { width, height } = container.getBoundingClientRect();
chart.resize(width, height);
chart.redraw();
}, 250);

window.addEventListener("resize", recalcLayout);

Trigger infinite scroll loading​

Load the next page of content when the user stops scrolling near the bottom. Essential for social feeds, product listings, and content-heavy pages.

const checkInfiniteScroll = debounce(() => {
const { scrollTop, scrollHeight, clientHeight } = container;
const nearBottom = scrollTop + clientHeight >= scrollHeight - 200;

if (nearBottom && !isLoading) {
loadNextPage();
}
}, 150);

container.addEventListener("scroll", checkInfiniteScroll);

Debounce drag-end to finalize drop position​

Wait for the user to settle on a drop position before committing the reorder. Perfect for sortable lists and kanban boards where rapid dragging occurs.

const finalizeDrop = debounce((targetIndex: number) => {
commitReorder(draggedItem, targetIndex);
clearDropIndicators();
saveOrder();
}, 200);

sortableList.addEventListener("dragover", (e) => {
const targetIndex = getInsertIndex(e.clientY);
showDropIndicator(targetIndex);
finalizeDrop(targetIndex);
});

Debounce content observer mutations​

Debounce MutationObserver callbacks to batch rapid DOM changes. Essential for CDK-style ContentObserver that watches for content changes in components.

const observeContent = (element: HTMLElement, callback: () => void) => {
const debouncedCallback = debounce(callback, 50);

const observer = new MutationObserver(debouncedCallback);
observer.observe(element, {
childList: true,
subtree: true,
characterData: true,
});

return () => observer.disconnect();
};

// Watch for content changes to recalculate overlay position
const disconnect = observeContent(triggerElement, () => {
repositionOverlay();
});

Debounce container resize for responsive components​

Debounce ResizeObserver callbacks to avoid layout thrashing during resize. Critical for CDK-style responsive components that adapt to container size.

const observeResize = (element: HTMLElement, callback: (rect: DOMRectReadOnly) => void) => {
const debouncedCallback = debounce((entries: ResizeObserverEntry[]) => {
const entry = entries[entries.length - 1];
callback(entry.contentRect);
}, 100);

const observer = new ResizeObserver(debouncedCallback);
observer.observe(element);

return () => observer.disconnect();
};

// Adapt component layout based on container width
observeResize(container, (rect) => {
const layout = rect.width < 400 ? "compact" : "full";
component.setLayout(layout);
});

Detect element overflow after content changes​

Check if text overflows its container after dynamic content updates. Essential for showing "..." tooltips or "Show more" buttons only when needed.

const checkOverflow = debounce(() => {
textElements.forEach((el) => {
const isOverflowing = el.scrollWidth > el.clientWidth;
el.classList.toggle("has-tooltip", isOverflowing);
el.title = isOverflowing ? el.textContent ?? "" : "";
});
}, 200);

// Re-check after content changes or window resize
window.addEventListener("resize", checkOverflow);