DEV Community

SAURAV KUMAR
SAURAV KUMAR

Posted on

๐Ÿšฆ What is Throttling in JavaScript?

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;

Enter fullscreen mode Exit fullscreen mode

๐Ÿ” Why this works (step-by-step)

  1. lastCall stores when fn last ran.
  2. On each event, we check current time (now).
  3. If now - lastCall >= wait, we run fn and update lastCall.
  4. 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;
}

Enter fullscreen mode Exit fullscreen mode

โœ… Step-by-step breakdown

  1. useState keeps the current window size.
  2. Inside useEffect, we create a throttled handleResize using our throttle helper.
  3. We add handleResize to the resize event.
  4. 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;
}

Enter fullscreen mode Exit fullscreen mode

โœจ Why useRef helps

  • lastCallRef persists across renders without triggering re-renders.
  • The handlerโ€™s internal state remains stable across the component lifecycle.

โš ๏ธ Common mistakes and misconceptions

  1. Confusing throttle with debounce โ€” pick based on need.
  2. Not cleaning up listeners โ€” always remove event listeners in cleanup.
  3. Recreating throttle on every render โ€” leads to losing internal state; use useRef or useCallback.
  4. Throttling API calls blindly โ€” if users expect instant feedback (search), debouncing may be better.
  5. Assuming throttle makes things โ€˜real-timeโ€™ โ€” throttle limits frequency but can delay responsiveness if wait is large.

๐Ÿงพ Real-world use cases

  • Throttling scroll handlers for sticky headers or analytics.
  • Throttling resize events 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 wait by 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)

Collapse
 
jarne profile image
Jarne

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?

Collapse
 
saurav_dev_2022 profile image
SAURAV KUMAR

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! ๐Ÿ™Œ

Collapse
 
neurolov__ai profile image
Neurolov AI

Throttling limits how often a function runs, keeping UIs smooth and preventing performance issues from rapid events like scroll or resize.

Collapse
 
neurolov__ai profile image
Neurolov AI

Excellent explanation this post nails the balance between clarity and depth. A perfect reference for anyone optimizing event-heavy UIs.

Collapse
 
saurav_dev_2022 profile image
SAURAV KUMAR

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! ๐Ÿš€