Debouncing and Throttling in React

Table of Contents

  1. Introduction
  2. What is Debouncing?
  3. What is Throttling?
  4. Comparing Debouncing vs Throttling
  5. Debouncing in React: Real Use Cases and Implementations
    • 5.1 Debouncing with Lodash
    • 5.2 Custom Debounce Hook
  6. Throttling in React: Real Use Cases and Implementations
    • 6.1 Throttling with Lodash
    • 6.2 Custom Throttle Hook
  7. Best Practices and Common Mistakes
  8. When to Use What?
  9. Conclusion

1. Introduction

React applications often handle user-driven, high-frequency events like typing, scrolling, and window resizing. Without control, these events can lead to:

  • Excessive state updates
  • Performance bottlenecks
  • API rate-limiting issues
  • Janky or unresponsive UI

To mitigate these issues, debouncing and throttling are used. Though similar in intent—to reduce frequency of execution—they differ in implementation and use cases.


2. What is Debouncing?

Debouncing ensures that a function is only invoked once the user has stopped triggering the event for a specified delay.

How it Works:

  1. A timer is set every time the event is fired.
  2. If the event is triggered again before the delay period ends, the previous timer is cleared.
  3. The function runs only once after the user pauses interaction.

Example Scenarios:

  • Live search input
  • Auto-saving form data
  • Input validation
  • Resize listener (when action is needed after resizing ends)

3. What is Throttling?

Throttling ensures a function is called at most once in every specified interval, regardless of how many times the event fires.

How it Works:

  1. A function runs immediately or after the first call.
  2. Subsequent calls are ignored until the defined interval has passed.

Example Scenarios:

  • Scroll tracking (e.g., infinite scroll)
  • Window resizing with frequent re-renders
  • Mouse move listeners

4. Comparing Debouncing vs Throttling

FeatureDebouncingThrottling
Execution timingAfter user stops triggering eventAt regular intervals
FrequencyOnce after a delayRepeatedly at controlled intervals
Use caseTyping/search inputScroll, resize, drag, mouse move
ComplexitySimpler behaviorSlightly more nuanced timing

5. Debouncing in React: Real Use Cases and Implementations

5.1 Debouncing with Lodash

Install lodash if not already:

bashCopyEditnpm install lodash

Use it like this:

jsxCopyEditimport { debounce } from 'lodash';
import { useState } from 'react';

const SearchInput = () => {
  const [query, setQuery] = useState('');

  const debouncedSearch = debounce((value) => {
    console.log("Searching for:", value);
    // API Call here
  }, 500);

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };

  return <input type="text" value={query} onChange={handleChange} />;
};

5.2 Custom Debounce Hook

Avoiding lodash? You can build your own hook:

jsxCopyEditimport { useEffect, useState } from 'react';

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

Usage:

jsxCopyEditconst SearchInput = () => {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);

  useEffect(() => {
    if (debouncedQuery) {
      console.log("Search:", debouncedQuery);
    }
  }, [debouncedQuery]);

  return <input value={query} onChange={e => setQuery(e.target.value)} />;
};

6. Throttling in React: Real Use Cases and Implementations

6.1 Throttling with Lodash

jsxCopyEditimport { throttle } from 'lodash';
import { useEffect, useState } from 'react';

const ScrollTracker = () => {
  const [scrollY, setScrollY] = useState(0);

  const handleScroll = throttle(() => {
    setScrollY(window.scrollY);
  }, 200);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  return <div>Scroll Y: {scrollY}</div>;
};

6.2 Custom Throttle Hook

jsxCopyEditexport const useThrottle = (callback, delay) => {
  const lastCall = useRef(0);

  return (...args) => {
    const now = Date.now();
    if (now - lastCall.current >= delay) {
      lastCall.current = now;
      callback(...args);
    }
  };
};

Usage Example:

jsxCopyEditconst handleScroll = useThrottle(() => {
  console.log('Throttled scroll event');
}, 300);

7. Best Practices and Common Mistakes

✅ Best Practices:

  • Use useCallback or useRef when working with debounced/throttled functions to persist the reference.
  • Clean up your listeners on unmount.
  • Always debounce API calls, not just the input event.

❌ Common Mistakes:

  • Not memoizing the debounced/throttled function — leads to multiple timers being created.
  • Forgetting to clear debounce/throttle effects on unmount — causes memory leaks.
  • Using throttle for typing events (instead of debounce) — wastes resources and creates weird behavior.

8. When to Use What?

ScenarioUse DebounceUse Throttle
Search bar
Window resize listener✅ (post-event handling) / ✅ throttle for live updates
Scroll position tracking
Mouse move handler
Auto-save on typing

9. Conclusion

Understanding debouncing and throttling can greatly improve your React application’s efficiency, UX, and performance—especially in complex, real-world use cases. Mastering these techniques allows developers to better manage event-based logic and optimize expensive renders or function calls.

To recap:

  • Use debounce for user inputs where waiting for a pause makes sense.
  • Use throttle for constant events like scrolling or resizing.

In upcoming modules, we’ll use both techniques in projects involving search filtering, infinite scroll, and dashboard UIs.