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");
fn until calls stop.fn after delay ms of silence. apply keeps the correct this.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");
fn at most once per interval.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)");
leading: true for immediate first call.fn (trailing 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.
Click "Run Code" to see the output here.
Code Explanation
The complete example stays visible. Use Previous and Next to move through its logical blocks.
Select Next to begin exploring the code.