Skip to content

React Performance: Debouncing & Throttling

Posted on:January 23, 2026 at 10:00 AM

Hey everyone! đź‘‹

Today we’re going to talk about something that can save your app from crashing and your API from crying: Debouncing and Throttling.

If you’ve ever built a search bar that fires a request on every single keystroke, you know the pain. The user types “React”, and you send 5 requests: “R”, “Re”, “Rea”, “Reac”, “React”.

That’s wasteful. We need to tell our app to “chill out” a bit.

Let’s dive in.


The Problem: Event Overload

Imagine a user resizing their browser window. This fires the resize event hundreds of times per second. If you have some heavy calculation running on that event, your browser is going to freeze.

Or the search bar example. If you search for “Supercalifragilisticexpialidocious”, do you really want to hit your backend 34 times?

No. You want to hit it once, when the user stops typing.

Debounce: “Wait until they stop”

Debouncing means: “Wait for a pause in the events before doing something.”

Think of an elevator. The door stays open as long as people keep walking in. It only closes and moves when no one has entered for a few seconds.

Creating a useDebounce Hook

We can create a custom hook using standard React hooks (useState and useEffect).

import { useState, useEffect } from 'react';

// This hook ensures that 'value' is only updated
// if 'delay' milliseconds have passed since the last change.
export function useDebounce<T>(value: T, delay: number): T {
  // State and setters for debounced value
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(
    () => {
      // Update debounced value after delay
      const handler = setTimeout(() => {
        setDebouncedValue(value);
      }, delay);

      // Cancel the timeout if value changes (also on delay change or unmount)
      // This is how we prevent debounced value from updating if value is changed ...
      // .. within the delay period. Timeout gets cleared and restarted.
      return () => {
        clearTimeout(handler);
      };
    },
    [value, delay] // Only re-call effect if value or delay changes
  );

  return debouncedValue;
}

How to use it

Here is a simple Search component.

import React, { useState, useEffect } from 'react';
import { useDebounce } from './useDebounce'; // Assuming you saved it here

const Search = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const [results, setResults] = useState([]);

  // This value will only update 500ms AFTER the user stops typing
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearchTerm) {
      console.log('Searching for:', debouncedSearchTerm);
      // fetchAPI(debouncedSearchTerm).then(...)
    } else {
      setResults([]);
    }
  }, [debouncedSearchTerm]); // Only trigger API call when DEBOUNCED value changes

  return (
    <div>
      <input
        type="text"
        placeholder="Search..."
        onChange={(e) => setSearchTerm(e.target.value)}
      />
    </div>
  );
};

Now, if you type “React” quickly, the effect only runs once!


Throttle: “Once every X seconds”

Throttling means: “Execute this function at most once every X milliseconds.”

Think of a machine gun (or a rate limiter). No matter how fast you pull the trigger, it only fires at a specific rate.

Throttling is great for infinite scrolling (checking scroll position) or window resizing. You don’t need to check the scroll position every pixel, checking it every 100ms is usually enough.

When to use which?


Conclusion

Performance isn’t just about fast algorithms; it’s about not doing unnecessary work.

By using Debouncing and Throttling, you make your React applications feel snappier and reduce the load on your servers. It’s a win-win.

So next time you add an onChange handler, ask yourself: “Do I really need to run this every time?”

Happy coding! 🚀