DEV Community

Shubham Khan
Shubham Khan

Posted on

Enhancing Performance in JavaScript: Understanding Debouncing and Throttling

Table of Contents


Performance optimization is critical in modern web applications, especially those that involve user interactions like typing in a search bar, scrolling, or resizing a window. These actions can fire off many function calls in a short time, which can degrade performance.

To mitigate this, two common techniques are debouncing and throttling, which allow you to control the rate at which a function is invoked, leading to a smoother, more efficient experience.

Debouncing: A Strategic Delay

Debouncing delays the execution of a function until a specified time has passed since the last event trigger. It is particularly helpful when dealing with events like search inputs, where you want to avoid making an API request on every keystroke.

How Debouncing Works

Imagine a search input where you want to wait until the user has stopped typing for 300ms before making an API request. Debouncing allows you to ensure that the function is only executed after the user has paused typing, preventing unnecessary API calls.

Debouncing Example

function debounce(func, delay) {
  let timeout;
  return function () {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), delay);
  };
}
function searchAPI() {
  console.log("API request made");
}
const debouncedSearch = debounce(searchAPI, 300);
debouncedSearch(); // Only triggers 300ms after the last call
Enter fullscreen mode Exit fullscreen mode

Here, the API request will only be made if the user pauses for 300ms.

Throttling: Controlling Event Frequency

In contrast to debouncing, throttling ensures that a function is called at most once every specified interval, even if the event continues to trigger. This technique is ideal for scenarios like window resizing or scrolling, where the events fire continuously.

How Throttling Works

Throttling allows a function to execute only once during a defined period (e.g., 200ms), ensuring that the function is not overwhelmed by repeated triggers.

Throttling Example

function throttle(func, limit) {
  let lastFunc;
  let lastRan;
  return function () {
    const context = this;
    const args = arguments;
    if (!lastRan) {
      func.apply(context, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(() => {
        if (Date.now() - lastRan >= limit) {
          func.apply(context, args);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  };
}
function updateLayout() {
  console.log("Layout updated");
}
const throttledUpdate = throttle(updateLayout, 200);
window.addEventListener("resize", throttledUpdate);
Enter fullscreen mode Exit fullscreen mode

In this example, the layout update function will only be called once every 200ms during window resizing.

Implementing in React: Debounce and Throttle with Custom Hooks

In React, we can use custom hooks to make the debounce and throttle functionality reusable across components. This enhances modularity and optimizes performance in various interactions.

Custom Hook for Debouncing

import { useRef, useCallback } from "react";
const useDebounce = (func, delay) => {
  const timer = useRef(null);
  return useCallback(
    (...args) => {
      if (timer.current) {
        clearTimeout(timer.current);
      }
      timer.current = setTimeout(() => func(...args), delay);
    },
    [func, delay]
  );
};
export default useDebounce;
Enter fullscreen mode Exit fullscreen mode

Using the Debounce Hook

import React, { useState } from "react";
import useDebounce from "./useDebounce";
const SearchComponent = () => {
  const [searchTerm, setSearchTerm] = useState("");

  const fetchResults = (query) => {
    console.log(`Fetching results for ${query}`);
    return new Promise((resolve) => setTimeout(resolve, 1000));
  };
  const debouncedFetch = useDebounce(fetchResults, 300);
  const handleSearch = (e) => {
    setSearchTerm(e.target.value);
    debouncedFetch(e.target.value);
  };
  return <input value={searchTerm} onChange={handleSearch} placeholder="Search..." />;
};
export default SearchComponent;
Enter fullscreen mode Exit fullscreen mode

Custom Hook for Throttling

import { useRef, useCallback } from "react";
const useThrottle = (func, limit) => {
  const lastRun = useRef(Date.now());
  return useCallback(
    (...args) => {
      const now = Date.now();
      if (now - lastRun.current >= limit) {
        func(...args);
        lastRun.current = now;
      }
    },
    [func, limit]
  );
};
export default useThrottle;
Enter fullscreen mode Exit fullscreen mode

Using the Throttle Hook

import React, { useEffect } from "react";
import useThrottle from "./useThrottle";

const ScrollComponent = () => {
  const handleScroll = () => {
    console.log("Scrolled!");
  };
  const throttledScroll = useThrottle(handleScroll, 500);
  useEffect(() => {
    window.addEventListener("scroll", throttledScroll);
    return () => window.removeEventListener("scroll", throttledScroll);
  }, [throttledScroll]);
  return <div style={{ height: "200vh" }}>Scroll down to see the effect</div>;
};
export default ScrollComponent;
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

Both debouncing and throttling are indispensable techniques to enhance performance in modern applications. While debouncing is ideal for inputs like search fields, throttling is best suited for high-frequency events like scrolling. Custom hooks in React, like useDebounce and useThrottle, make these optimizations easy to implement across your app, ensuring a more efficient, responsive experience.

Top comments (0)