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 (0)