You build a small React dashboard and notice the browser lags when you resize the window or scroll fast. The charts stutter, buttons freeze, and users get annoyed. I hit this exact problem once โ charts were re-rendering on every tiny resize event and users with slower machines felt like they were dragging through molasses.
The fix was small but effective: throttling. We limited how often the expensive work ran, and the app felt smooth again. In this post, Iโll explain why throttling matters, how it works, and how to use it in React โ in plain English.
๐งฉ Why throttling matters (the โwhyโ before the โhowโ)
Think of throttling like a traffic cop at a busy crosswalk. Without the cop, everyone rushes across and causes chaos. With the cop, one person crosses every few seconds โ orderly and predictable.
In web apps, events like scroll, resize, mousemove, and input can fire dozens or hundreds of times per second. If your handler does heavy work (re-render, compute layout, call API), running it on every event will:
- slow down the browser (CPU spikes),
- cause many unnecessary network calls, and
- make the UI feel janky.
Throttling gives you control: run the handler at most once every N milliseconds. That reduces work and keeps the UI responsive.
๐ง Throttle vs Debounce โ quick friendly analogy
-
Throttle: A guard lets one person through every N seconds. Even if 100 people wait, one goes through on schedule.
Good for:
scroll,resize, periodic analytics. - Debounce: A timer waits for silence, then lets the last person through. If people keep coming, it keeps waiting. Good for: search-as-you-type where you want to wait until typing stops.
Both are useful โ pick based on the problem.
๐ ๏ธ Simple throttle implementation (plain JS)
Letโs build a small, easy-to-read throttle function.
// throttle.js
function throttle(fn, wait = 200) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= wait) {
lastCall = now;
fn.apply(this, args);
}
};
}
export default throttle;
๐ Why this works (step-by-step)
-
lastCallstores whenfnlast ran. - On each event, we check current time (
now). - If
now - lastCall >= wait, we runfnand updatelastCall. - Otherwise, we ignore this event.
This is a leading-edge throttle โ it runs immediately, then blocks until wait ms pass.
๐ React use case: Throttle window resize
Imagine you want to update a chart layout on window resize. Resizing fires continuously as a user drags the corner โ we only need to update at most once every 200ms.
// useWindowSizeThrottled.jsx
import { useEffect, useState } from "react";
import throttle from "./throttle";
export default function useWindowSizeThrottled(wait = 200) {
const isClient = typeof window !== "undefined";
const [size, setSize] = useState({
width: isClient ? window.innerWidth : 0,
height: isClient ? window.innerHeight : 0,
});
useEffect(() => {
const handleResize = throttle(() => {
setSize({ width: window.innerWidth, height: window.innerHeight });
}, wait);
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, [wait]);
return size;
}
โ Step-by-step breakdown
-
useStatekeeps the current window size. - Inside
useEffect, we create a throttledhandleResizeusing ourthrottlehelper. - We add
handleResizeto theresizeevent. - On cleanup, we remove the listener to avoid leaks.
This prevents setSize from being called dozens of times per second, reducing re-renders and CPU usage.
โ๏ธ Improve the throttle for React (useRef approach)
If your component re-renders often, recreating the throttle could cause surprising behavior. useRef keeps stable state without causing re-renders:
import { useEffect, useRef, useState } from "react";
function useWindowSizeThrottledRef(wait = 200) {
const isClient = typeof window !== "undefined";
const [size, setSize] = useState({
width: isClient ? window.innerWidth : 0,
height: isClient ? window.innerHeight : 0,
});
const lastCallRef = useRef(0);
useEffect(() => {
function handler() {
const now = Date.now();
if (now - lastCallRef.current >= wait) {
lastCallRef.current = now;
setSize({ width: window.innerWidth, height: window.innerHeight });
}
}
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
}, [wait]);
return size;
}
โจ Why useRef helps
-
lastCallRefpersists across renders without triggering re-renders. - The handlerโs internal state remains stable across the component lifecycle.
โ ๏ธ Common mistakes and misconceptions
- Confusing throttle with debounce โ pick based on need.
- Not cleaning up listeners โ always remove event listeners in cleanup.
-
Recreating throttle on every render โ leads to losing internal state; use
useReforuseCallback. - Throttling API calls blindly โ if users expect instant feedback (search), debouncing may be better.
-
Assuming throttle makes things โreal-timeโ โ throttle limits frequency but can delay responsiveness if
waitis large.
๐งพ Real-world use cases
- Throttling
scrollhandlers for sticky headers or analytics. - Throttling
resizeevents for layout recalculation. - Limiting telemetry or analytics to avoid server-side throttling.
- Rate-limiting cursor-movement handlers in web games.
- Controlling UI updates from frequent streams or websockets.
๐ฏ Bonus: How to Explain Throttling in an Interview
When an interviewer asks, โWhat is throttling?โ keep it short and show a quick example.
๐ฃ๏ธ Short answer (30s)
Throttling limits how often a function can run โ at most once per given interval โ which prevents expensive handlers from running too frequently and improves UI performance.
๐ป Quick example to show
Sketch or paste the simple throttle(fn, wait) code and explain lastCall + Date.now() logic.
๐ Common follow-ups & answers
- Q: How is throttle different from debounce? A: Throttle enforces a steady pace; debounce waits for silence.
- Q: Leading vs trailing throttle? A: Leading runs immediately then waits; trailing runs at the end of the burst. You can implement options for both.
- Q: Edge cases? A: Time drift, handler identity across renders, and missing final call after a burst โ be prepared to discuss solutions.
๐งฉ Mini whiteboard challenge
Design throttle(fn, wait, options) where options may include leading and trailing. Sketch a timeline showing when fn runs during a burst of events.
๐ง Key takeaways
- Throttle = run at most once every N ms โ great for periodic limits.
- Debounce = run after events stop โ great for waiting until user is done typing.
- In React, keep handlers stable (
useRef,useCallback) and always remove listeners. - Choose
waitby testing on real devices โ 100โ300ms is a common range for UI work. - Keep user expectations in mind โ throttling reduces CPU/network but may delay responsiveness.
๐ About Me
Hi, I'm Saurav Kumar โ a Software Engineer passionate about building modern web and mobile apps using JavaScript, TypeScript, React, Next.js, and React Native.
Iโm exploring how AI tools can speed up development,
and I share beginner-friendly tutorials to help others grow faster.
๐ Connect with me:
LinkedIn โ I share short developer insights and learning tips
GitHub โ Explore my open-source projects and experiments
If you found this helpful, share it with a friend learning JavaScript โ it might help them too.
Until next time, keep coding and keep learning ๐
Top comments (5)
Wouldn't it be quite handy to combine throttle and denounce for resize events? With throttling we guarantee that the content gets updated regularly while with the debounce we guarantee that after the burst of events the component gets rendered with the final size?
Hey Jarne โ thatโs a fantastic point ๐
Yes, combining both throttle + debounce is a great strategy for resize events.
It gives you the best of both worlds โ smooth updates while resizing (throttle) and a final precise render when it stops (debounce).
I actually explored that idea further in a follow-up blog where I built a mini React project that uses both together โ debounce for search and throttle for infinite scroll.
Hereโs the link if youโd like to check it out:
๐ Debounce vs Throttle โ How to Choose & Build One Mini Project
Thanks for raising such a thoughtful point โ it adds real value to the discussion! ๐
Throttling limits how often a function runs, keeping UIs smooth and preventing performance issues from rapid events like scroll or resize.
Excellent explanation this post nails the balance between clarity and depth. A perfect reference for anyone optimizing event-heavy UIs.
Thank you so much, Neurolov AI ๐
That means a lot! I try to keep things beginner-friendly while still giving useful takeaways for real-world projects.
Glad you found it clear and helpful! ๐