What you'll be able to do
By the end of this page you should be able to explain throttle vs debounce, write a simple throttle helper, and pick throttle when you need steady updates during continuous events.
Previous post: debounce from zero.
Who this is for
- People updating a sticky header or progress bar on scroll
- Beginners who mixed up debounce and throttle in interviews
- Anyone whose mousemove handler floods the console
You can skip this if the difference is already muscle memory. We build the small version on purpose.
Debounce vs throttle (two pictures)
Debounce: wait for quiet, then run once. Great for search-as-you-type.
Throttle: while noise continues, run at most once per window (for example every 100ms). Great for scroll position UI.
Scenario A — typing a query. Debounce — you care about the final string.
Scenario B — scrolling a long page. Throttle — you want periodic updates, not one update after the user stops.
A small throttle helper
function throttle(fn, waitMs) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= waitMs) {
last = now;
fn.apply(this, args);
}
};
}
Scenario A — scroll listener logs window.scrollY at most every 200ms.
Scenario B — resize recalculates layout hints without firing hundreds of times per drag.
This leading-edge style runs immediately, then ignores calls until the window passes. Other variants queue a trailing call — learn this shape first.
When not to throttle
Scenario A — form submit. One click should mean one intentional action (also disable the button).
Scenario B — critical toggle. Do not drop events that must not be lost; throttle is for dense streams.
A tiny practice file
<!DOCTYPE html>
<html>
<body style="height: 2000px">
<p id="out" style="position: sticky; top: 0; background: #fff">scroll</p>
<script>
function throttle(fn, waitMs) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= waitMs) {
last = now;
fn.apply(this, args);
}
};
}
const out = document.getElementById("out");
window.addEventListener(
"scroll",
throttle(function () {
out.textContent = "y=" + Math.round(window.scrollY);
}, 200),
);
</script>
</body>
</html>
Scroll fast — the label updates in steps, not every pixel.
Mistakes I see a lot
1. Using debounce language for scroll UI and wondering why the bar only moves after stopping.
2. Throttle window so large the UI feels dead (try 100–200ms for scroll chrome).
3. Recreating throttle inside the listener every time — create once.
4. Assuming throttle guarantees the last event runs — this simple version may skip the final scroll position; advanced versions add a trailing call.
What to try before the next post
- Compare the same scroll page with debounce vs throttle mentally (or both demos).
- Throttle a mousemove logger.
- Write one sentence: "I debounce ___ and throttle ___."
Next in this series: memoization from zero — cache pure function results so repeat inputs skip repeat work.
Try this next outside the series
Deeper JavaScript pays off when you connect it to routes, imports, and performance-sensitive app code.
- Next.js route handlers — see modules and request logic in a production-shaped route file
- HTML to JSX Converter — use the tool when deeper syntax work starts touching React files