Academic Block

Debouncing & Throttling Get Certified

When an event fires very rapidly (typing, scrolling, resizing), running your handler on every single event can make the UI feel sluggish. Debouncing and throttling are two techniques for controlling how often a function actually runs.

What Is Debouncing?

What this code does: Creates a debounce helper that only runs the function after calls have stopped for a set delay.

function debounce(fn, delay) {
    let timeoutId = null;
    return function (...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}

function handleInput(value) {
    console.log("Searching for:", value);
}

const debouncedSearch = debounce(handleInput, 300);

console.log("Simulating rapid typing...");
debouncedSearch("j");
debouncedSearch("ja");
debouncedSearch("jav");
debouncedSearch("java");
console.log("Only the last call ('java') will actually run after 300ms");

What Is Throttling?

What this code does: Creates a throttle helper that runs the function at most once every fixed interval.

function throttle(fn, interval) {
    let lastCallTime = 0;
    return function (...args) {
        const now = Date.now();
        if (now - lastCallTime >= interval) {
            lastCallTime = now;
            fn.apply(this, args);
        }
    };
}

function handleScroll(position) {
    console.log("Scroll position handled:", position);
}

const throttledScroll = throttle(handleScroll, 200);

console.log("Simulating rapid scroll events...");
throttledScroll(50);
throttledScroll(100);
throttledScroll(150);
throttledScroll(200);
throttledScroll(250);
console.log("Only calls that are at least 200ms apart actually run");

Debounce vs Throttle Side by Side

What this code does: Runs the same events through both debounce and throttle so you can compare the results.

function debounce(fn, delay) {
    let timeoutId = null;
    return function (...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn.apply(this, args), delay);
    };
}

function throttle(fn, interval) {
    let lastCallTime = 0;
    return function (...args) {
        const now = Date.now();
        if (now - lastCallTime >= interval) {
            lastCallTime = now;
            fn.apply(this, args);
        }
    };
}

const debounced = debounce((n) => console.log("Debounced fired at call:", n), 250);
const throttled = throttle((n) => console.log("Throttled fired at call:", n), 250);

console.log("--- Calling both helpers with events 1 to 6 ---");
for (let i = 1; i <= 6; i++) {
    console.log("Event", i);
    debounced(i);
    throttled(i);
}
console.log("Debounced waits for silence; throttled fires regularly");

Leading vs Trailing Debounce

What this code does: Shows a leading-edge debounce that fires immediately on the first call.

function debounce(fn, delay, { leading = false } = {}) {
    let timeoutId = null;
    return function (...args) {
        const isFirstCall = !timeoutId;
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            timeoutId = null;
            if (!leading) fn.apply(this, args);
        }, delay);
        if (leading && isFirstCall) {
            fn.apply(this, args);
        }
    };
}

const onClick = debounce(
    () => console.log("Button action ran!"),
    500,
    { leading: true }
);

console.log("Simulating fast double-clicks...");
onClick();
onClick();
onClick();
console.log("Only the first click runs immediately (leading edge)");

Real-World Use Cases

What this code does: Shows typical uses: debounced search and throttled resize handling.

function debounce(fn, delay) {
    let timeoutId = null;
    return function (...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn.apply(this, args), delay);
    };
}

function throttle(fn, interval) {
    let lastCallTime = 0;
    return function (...args) {
        const now = Date.now();
        if (now - lastCallTime >= interval) {
            lastCallTime = now;
            fn.apply(this, args);
        }
    };
}

// Debounce: wait until user stops typing
const searchAPI = debounce((query) => {
    console.log("Fetching results for:", query);
}, 300);

// Throttle: limit expensive layout recalculation
const recalcLayout = throttle(() => {
    console.log("Recalculating layout...");
}, 200);

searchAPI("hello world");
recalcLayout();
console.log("Handlers wired up (simulated)");

Debouncing vs Throttling at a Glance

Feature Debouncing Throttling
When it runs Once, after calls stop At most once per interval
Fires during a burst? No — waits for silence Yes — regularly
Good for Search, autosave, form validation Scroll, resize, mouse-move
Guarantees regular execution? No Yes

Common Mistakes

  • Using debounce when you need continuous feedback (e.g. live drag)
  • Using throttle when you only care about the final value
  • Creating a new debounced/throttled function on every call
  • Forgetting to clear pending timers on cleanup
  • Choosing a delay that is too long or too short

Best Practices

  • Use debounce when you only care about the final state
  • Use throttle when you need steady updates during activity
  • Create the wrapper once and reuse it
  • Typical delays: 150–500 ms for input tasks
  • Consider leading-edge for instant first-action feedback

Quick Summary

  • Debounce → wait until calls stop, then run once
  • Throttle → run at most once per interval
  • Debounce for search / autosave; throttle for scroll / resize
  • Create the helper once and reuse it

Try It Yourself

What this code does: Simulates a burst of events and shows which ones are handled by debounce vs throttle.

function debounce(fn, delay) {
    let timeoutId = null;
    return function (...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn.apply(this, args), delay);
    };
}

function throttle(fn, interval) {
    let lastCallTime = 0;
    return function (...args) {
        const now = Date.now();
        if (now - lastCallTime >= interval) {
            lastCallTime = now;
            fn.apply(this, args);
        }
    };
}

function handleEvent(label, n) {
    console.log(label, "handled event", n);
}

const debouncedHandler = debounce((n) => handleEvent("Debounced:", n), 300);
const throttledHandler = throttle((n) => handleEvent("Throttled:", n), 300);

console.log("Simulating 8 rapid events...");
for (let i = 1; i <= 8; i++) {
    console.log("Event", i);
    debouncedHandler(i);
    throttledHandler(i);
}
console.log("Debounced waits; throttled fires regularly");
Tip: Ask yourself: “Do I need every step, or just the final result?” Every step → throttle. Final result → debounce.

🧪 Test Your JavaScript Code

Edit the code on the left and click "Run Code" to see the console output on the right.

📝 JavaScript Code
👁 Console Output

Click "Run Code" to see the output here.

Ctrl + Enter to run code Esc to close