Debounce, Throttle, Cancel – Optimizing API Calls Like a Pro

Faiaz KhanFaiaz Khan
3 min read

We’ve all done it.
Tied an onChange to an API call. Typed a letter.
And boom — 10 requests in under 3 seconds.

Your server? Crying.
Your app? Lagging.
Your recruiter watching your portfolio? Closing the tab.

Today we fix that.
We’re diving into:

  • Why API overcalling happens

  • What debounce, throttle, and cancel actually do

  • Real React patterns to apply them

  • And some gotchas no one warns you about


The Problem: Too Many API Calls

Common Culprits:

  • Search inputs (onChangefetch)

  • Scroll or resize listeners

  • Auto-save features

  • Live filtering / autocomplete

Without guards, these become network abuse.
Worse: results arrive out of order and your UI starts flickering like a strobe light.


Debounce vs Throttle vs Cancel – The Trio Explained

MethodWhat It DoesBest For
DebounceWaits for a pause before callingSearch bars, typing
ThrottleEnsures calls happen at a fixed rateScroll, resize
CancelAborts in-progress callsRapid input, route changes

Debounce – Let Them Finish Typing, Please

📦 Pattern:

Only make the API call after the user stops typing for a bit.

import { useEffect, useState } from 'react';

function useDebouncedValue(value: string, delay: number) {
  const [debounced, setDebounced] = useState(value);

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

  return debounced;
}

Now use it like:

const [query, setQuery] = useState('');
const debouncedQuery = useDebouncedValue(query, 500);

useEffect(() => {
  if (debouncedQuery) fetchData(debouncedQuery);
}, [debouncedQuery]);

You reduced API load without sacrificing UX.


Throttle – One Call Per Interval, No Matter What

function throttle(func, limit) {
  let inThrottle = false;
  return function (...args) {
    if (!inThrottle) {
      func.apply(this, args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

Useful for:

  • Tracking scroll position

  • Resizing windows

  • Events that fire constantly but don’t need real-time reaction


Cancel – Prevent Overlapping or Stale Requests

Using AbortController:

useEffect(() => {
  const controller = new AbortController();

  fetch(`/api/search?q=${query}`, { signal: controller.signal })
    .then(res => res.json())
    .then(setData)
    .catch(err => {
      if (err.name !== 'AbortError') console.error(err);
    });

  return () => controller.abort();
}, [query]);

When to Cancel:

  • User changes input before response comes back

  • Route/page changes mid-request

  • Revalidating stale data

This is how you avoid "response from previous query overwriting new data."


Common Pitfalls

ProblemFix
Request from old input overwrites new resultsUse AbortController
Debounce too short = still spam300–600ms is the sweet spot
Forgetting cleanup in useEffectAlways return a cleanup function
Overusing throttle on sensitive UIThrottle carefully — it feels laggy if abused

Bonus: Use Libraries to Save Time

  • Lodashdebounce & throttle baked in

  • React Query – Handles canceling, caching, retries

  • SWR – Handles deduping and revalidation

  • [axios.CancelToken (deprecated) → use AbortController instead now


Final Thoughts

Your API calls shouldn’t:

  • Choke your server

  • Race each other like Tokyo Drift

  • Make your UI look like a glitchy game

With debounce, throttle, and cancel, you:

  • Keep things smooth

  • Stay performant

  • And look like a dev who respects rate limits


📚 This is Awaiting Response(),
Where we don’t just fetch — we fetch with grace, speed, and dignity.

Next up:
Caching Like You Mean It – SWR, React Query & Beyond
Because sometimes, the best API call… is the one you don’t make 😎

0
Subscribe to my newsletter

Read articles from Faiaz Khan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Faiaz Khan
Faiaz Khan

Hey! I'm Faiaz — a frontend developer who loves writing clean, efficient, and readable code (and sometimes slightly chaotic code, but only when debugging). This blog is my little corner of the internet where I share what I learn about React, JavaScript, modern web tools, and building better user experiences. When I'm not coding, I'm probably refactoring my to-do list or explaining closures to my cat. Thanks for stopping by — hope you find something useful or mildly entertaining here.