Skip to main content

throttle()

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

Creates a throttled function that invokes at most once per wait period.

note

Executes on leading edge (immediately) and trailing edge (after 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 throttle.

wait: number​

The number of milliseconds to throttle invocations.


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

The throttled function with a cancel() method.


Throws​

RangeError If wait is negative or not finite.


Since​

2.0.0


Note​

Call .cancel() to clear any pending execution.


Performance​

Uses timestamp comparison to avoid unnecessary setTimeout calls. Stores latest args/context for trailing edge execution.


Also known as​

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


Example​

// Scroll handler
const handleScroll = throttle(() => {
console.log('Scrolling...');
}, 200);

window.addEventListener('scroll', handleScroll);

// With arguments
const logMouse = throttle((x: number, y: number) => {
console.log('Mouse:', x, y);
}, 100);

document.addEventListener('mousemove', (e) => {
logMouse(e.clientX, e.clientY);
});

// Cancel pending
logMouse.cancel();

How it works?​

Throttle enforces a maximum execution rate β€” the function fires at most once per interval. Calls during the cooldown are ignored, ensuring consistent rhythm regardless of input frequency.

Throttle vs Debounce​

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

Throttle vs Debounce comparison

ThrottleDebounce
FiresPeriodicallyOnce, after silence
Best forScroll eventsSearch input

Use Cases​

Limit function execution frequency πŸ“Œβ€‹

Control the rate of function execution to prevent performance issues. Essential for performance optimization and preventing excessive resource usage.

const logScroll = throttle(() => {
console.log("Scrolled!");
}, 100);

window.addEventListener("scroll", logScroll);

Optimize scroll and resize events​

Limit the frequency of scroll and resize event handlers. Critical for responsive design and scroll-based UI. Limit animation updates to maintain smooth performance. Essential for smooth animations and visual effects.

const updateCanvas = throttle((mouseX: number, mouseY: number) => {
ctx.clearRect(0, 0, width, height);
drawParticles(mouseX, mouseY);
}, 33); // Cap at ~30fps

canvas.addEventListener("mousemove", (e) => updateCanvas(e.offsetX, e.offsetY));

Rate-limit ticket purchases during high-demand sales​

Throttle ticket purchase requests to ensure fair access during flash sales. Essential for concert ticketing, sports events, and limited availability sales.

// Throttle purchase attempts to 1 per second per user
const attemptPurchase = throttle(async (ticketId, userId) => {
const result = await ticketingAPI.purchase({ ticketId, userId });
if (result.success) {
showConfirmation(result.ticket);
} else {
showWaitlistOption();
}
}, 1000);

buyButton.addEventListener("click", () => {
attemptPurchase(selectedTicketId, currentUserId);
});

// Throttle seat selection updates to reduce server load
const updateSeatSelection = throttle((seats) => {
api.reserveSeats(seats);
updateSeatMap(seats);
}, 500);

seatPicker.on("selection_change", updateSeatSelection);

Smooth canvas drawing with pointer tracking​

Throttle pointer move events to control brush stroke density on a canvas. Essential for drawing apps, signature pads, and annotation tools.

const drawStroke = throttle((x: number, y: number) => {
ctx.lineTo(x, y);
ctx.stroke();
}, 16); // ~60fps

canvas.addEventListener("pointermove", (e) => {
if (isDrawing) {
drawStroke(e.offsetX, e.offsetY);
}
});

Limit gesture recognition updates on mobile​

Throttle touch events during pinch-to-zoom or swipe gestures. Critical for mobile web apps handling complex multi-touch interactions.

const handlePinch = throttle((scale: number, centerX: number, centerY: number) => {
applyZoom(scale, centerX, centerY);
updateMinimap();
}, 50);

element.addEventListener("touchmove", (e) => {
if (e.touches.length === 2) {
const scale = getDistance(e.touches[0], e.touches[1]) / initialDistance;
const center = getMidpoint(e.touches[0], e.touches[1]);
handlePinch(scale, center.x, center.y);
}
});

Track drag-and-drop position updates​

Throttle drag events to update drop zone indicators without overwhelming the renderer. Essential for sortable lists, kanban boards, and drag-and-drop builders.

const onDrag = throttle((e: DragEvent) => {
const dropTarget = getDropTarget(e.clientX, e.clientY);
highlightDropZone(dropTarget);
updateDragPreview(e.clientX, e.clientY);
}, 32); // ~30fps is enough for visual feedback

draggable.addEventListener("drag", onDrag);

Update sticky header shadow on scroll​

Add or remove a shadow on a sticky header based on scroll position. Perfect for app shells and dashboard layouts with fixed navigation.

const updateHeaderShadow = throttle(() => {
const scrolled = window.scrollY > 0;
header.classList.toggle("elevated", scrolled);
}, 100);

window.addEventListener("scroll", updateHeaderShadow);

Throttle pull-to-refresh gesture detection​

Limit pull-to-refresh progress updates during touch drag. Critical for mobile web apps implementing native-like pull-to-refresh.

const onPullProgress = throttle((distance: number) => {
const progress = clamp(distance / PULL_THRESHOLD, 0, 1);
refreshIndicator.style.transform = `translateY(${distance}px) rotate(${progress * 360}deg)`;
refreshIndicator.style.opacity = String(progress);
}, 16);

container.addEventListener("touchmove", (e) => {
if (isPulling) {
const distance = e.touches[0].clientY - pullStartY;
onPullProgress(distance);
}
});

Dispatch scroll events to multiple overlay subscribers​

Throttle a centralized scroll dispatcher that notifies all active overlays. Essential for CDK-style ScrollDispatcher that manages overlay repositioning.

const activeOverlays: Array<{ reposition: () => void }> = [];

const scrollDispatcher = throttle(() => {
activeOverlays.forEach((overlay) => overlay.reposition());
}, 16);

// Single scroll listener for all overlays
window.addEventListener("scroll", scrollDispatcher, { passive: true });

// Overlays register/unregister
const registerOverlay = (overlay: { reposition: () => void }) => {
activeOverlays.push(overlay);
return () => {
const idx = activeOverlays.indexOf(overlay);
if (idx >= 0) activeOverlays.splice(idx, 1);
};
};

Limit virtual scroll position recalculations​

Throttle scroll handler in a virtual scroll implementation to reduce layout thrashing. Essential for rendering large lists (10k+ items) without performance degradation.

const onVirtualScroll = throttle(() => {
const scrollTop = container.scrollTop;
const startIndex = Math.floor(scrollTop / ROW_HEIGHT);
const endIndex = startIndex + Math.ceil(container.clientHeight / ROW_HEIGHT);

renderVisibleRows(startIndex, endIndex);
updateScrollbar(scrollTop);
}, 16); // 60fps

container.addEventListener("scroll", onVirtualScroll);