Master Code
On The Go

Learn. Practice. Build.

Home › Code Snippets › JavaScript Snippets › Debounce & Throttle

JavaScript Snippet

JavaScript Debounce & Throttle

The two patterns that make expensive events cheap — with interactive live demos.

Last updated: October 2026

Quick answer: Debounce delays a function until events stop firing — perfect for search inputs and form validation. Throttle runs a function at a fixed interval while events fire — perfect for scroll and resize handlers. Both dramatically reduce how often expensive code runs.

🎬 Interactive Live Demo

Type in the input boxes and watch the difference between debounce and throttle in real time.

💡 Type fast in both boxes — debounce runs once, throttle runs repeatedly at 500ms intervals.

1

What is Debounce?

Debounce delays a function call until after a specified delay has passed with no further calls. If new events keep firing, the timer resets.

Analogy: an elevator door that waits for the last person to step in before closing. Every new passenger resets the timer.

Use for:

  • Search input (only fetch after user stops typing)
  • Form validation on typing
  • Autosave while editing
  • Window resize handlers
2

What is Throttle?

Throttle limits a function to run at most once every N milliseconds, no matter how many times the event fires. Continuous events produce steady, predictable calls.

Analogy: a machine gun with a maximum rate of fire. You can hold the trigger, but bullets only come out at the defined rate.

Use for:

  • Scroll progress indicators
  • Mouse move tracking
  • Infinite scroll triggers
  • Rate-limiting API calls
3

The Debounce Function

Here is a clean, dependency-free implementation:

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

// Usage
const handleSearch = debounce((query) => {
    console.log("Searching for:", query);
}, 500);

document.querySelector("#search").addEventListener("input", (e) => {
    handleSearch(e.target.value);
});

Every keystroke clears the previous timer and starts a new one. Only after 500ms of silence does the function actually run.

4

The Throttle Function

Here's a version that fires on both leading and trailing edges:

function throttle(fn, delay) {
    let lastCall = 0;
    let timeoutId;

    return function(...args) {
        const now = Date.now();
        const remaining = delay - (now - lastCall);

        if (remaining <= 0) {
            if (timeoutId) {
                clearTimeout(timeoutId);
                timeoutId = null;
            }
            lastCall = now;
            fn.apply(this, args);
        } else if (!timeoutId) {
            timeoutId = setTimeout(() => {
                lastCall = Date.now();
                timeoutId = null;
                fn.apply(this, args);
            }, remaining);
        }
    };
}

// Usage
const handleScroll = throttle(() => {
    console.log("Scroll position:", window.scrollY);
}, 200);

window.addEventListener("scroll", handleScroll);

This ensures the function runs immediately on the first event, then at most every 200ms, and always fires one last time after events stop.

5

Real-World Example: Search Autocomplete

import { useState, useEffect } from "react";

function SearchBox() {
    const [query, setQuery] = useState("");
    const [results, setResults] = useState([]);

    useEffect(() => {
        if (!query) return;

        const controller = new AbortController();

        const timeoutId = setTimeout(() => {
            fetch(`/api/search?q=${query}`, { signal: controller.signal })
                .then(res => res.json())
                .then(setResults)
                .catch(err => {
                    if (err.name !== "AbortError") console.error(err);
                });
        }, 500);

        return () => {
            clearTimeout(timeoutId);
            controller.abort();
        };
    }, [query]);

    return (
        <div>
            <input
                value={query}
                onChange={e => setQuery(e.target.value)}
                placeholder="Search..."
            />
            <ul>
                {results.map(r => <li key={r.id}>{r.title}</li>)}
            </ul>
        </div>
    );
}

This combines debounce (via setTimeout) with AbortController to cancel in-flight requests. The result: one clean API call after the user stops typing.

👉 Read our AbortController Guide.

🎯 Debounce vs Throttle — Quick Reference

Aspect Debounce Throttle
When it runs After events stop At fixed intervals during events
Total calls 1 (after burst) Multiple (steady rate)
Best for Search, validation, autosave Scroll, resize, mouse tracking
Feels like "Wait until done" "Update regularly"

⚠️ Common Mistakes

❓ Frequently Asked Questions

What is the difference between debounce and throttle?

Debounce waits until events stop firing, then runs once. Throttle runs at a fixed interval while events fire. Use debounce for search inputs, throttle for scroll events.

When should I use debounce?

Use debounce when the user's final action matters more than intermediate ones. Examples: search input, form validation on typing, autosave, window resize handled at the end.

When should I use throttle?

Use throttle when you need consistent updates at a fixed rate. Examples: scroll progress indicators, mouse tracking, infinite scroll triggers, window resize during drag.

Does debounce or throttle improve performance?

Both improve performance by reducing how often expensive functions run. Debounce reduces calls when events are bursty (typing). Throttle reduces calls when events are continuous (scroll).

Can I use Lodash debounce and throttle instead?

Yes. Lodash provides battle-tested debounce and throttle with options like leading, trailing, and maxWait. If you already use Lodash, prefer those implementations. The custom versions here are dependency-free.

🎯 What's Next?

← All JavaScript Snippets