All Snippets Snippet JavaScript

Throttle Function

In Out Window 1 Window 2 1 per window

Limit function execution to fixed intervals during continuous events like scroll, drag, and resize.

D
Kunwar "AKA" AJ Sharing what I have learned
Feb 21, 2026 3 min JavaScript

The Pattern

throttle.js
function throttle(fn, intervalMs) {
    let lastTime = 0;
    let timerId = null;

    return function (...args) {
        const now = Date.now();
        const elapsed = now - lastTime;

        if (elapsed >= intervalMs) {
            // Enough time passed — fire immediately
            lastTime = now;
            fn.apply(this, args);
        } else {
            // Schedule a trailing call so the last event is never lost
            clearTimeout(timerId);
            timerId = setTimeout(() => {
                lastTime = Date.now();
                fn.apply(this, args);
            }, intervalMs - elapsed);
        }
    };
}

What Happens Under the Hood

Let us trace what happens when a user scrolls continuously for 1 second with a 200ms throttle:

execution-flow.txt
Time 0ms:    scroll event → elapsed >= 200ms? Yes (first call) → FIRE
Time 50ms:   scroll event → elapsed = 50ms, < 200ms → schedule trailing
Time 100ms:  scroll event → elapsed = 100ms → re-schedule trailing
Time 150ms:  scroll event → elapsed = 150ms → re-schedule trailing
Time 200ms:  scroll event → elapsed >= 200ms → FIRE immediately
Time 250ms:  scroll event → elapsed = 50ms → schedule trailing
...
Time 1000ms: scroll event → elapsed >= 200ms → FIRE
Time 1050ms: scrolling stops
Time 1200ms: trailing timer fires → FIRE (last position captured)

Without throttle: ~60 calls (every 16ms at 60fps)
With 200ms throttle: ~6 calls — evenly spaced, last event captured

The trailing call is what separates a good throttle from a naive one. Without it, the last event before the user stops gets silently dropped. That means scroll position, drag position, or resize dimensions could be stale. The trailing timer guarantees the final value always gets processed.

Why This Matters

debounce-vs-throttle.txt
Debounce:  waits until activity STOPS, then fires once
           → Best for: search input, form validation, auto-save

Throttle:  fires at regular INTERVALS during activity
           → Best for: scroll, drag, resize, mousemove

Scroll handler without throttle:
  60 position checks per second → layout thrashing → dropped frames

Scroll handler with 100ms throttle:
  10 position checks per second → smooth infinite scroll loading

Usage Example

usage.js
// Infinite scroll — check position every 200ms, not every frame
const handleScroll = throttle(() => {
    const scrollBottom = window.innerHeight + window.scrollY;
    const docHeight = document.documentElement.scrollHeight;

    if (docHeight - scrollBottom < 500) {
        loadMoreItems();
    }
}, 200);
window.addEventListener('scroll', handleScroll);

// Drag handler — update position smoothly without overload
const handleDrag = throttle((e) => {
    updateElementPosition(e.clientX, e.clientY);
    sendPositionToServer(e.clientX, e.clientY);
}, 50);
element.addEventListener('mousemove', handleDrag);

// Analytics — track engagement without flooding the endpoint
const trackScroll = throttle(() => {
    analytics.track('scroll_depth', getScrollPercentage());
}, 2000);

Use throttle when you need regular updates during continuous user activity. The interval depends on the use case: 50 to 100 milliseconds for visual feedback like drag handlers, 200 to 500 milliseconds for data loading like infinite scroll, and 1000 milliseconds or more for analytics tracking. If you only care about the final value after activity stops, use debounce instead.