Dynavo Labs
HomeAbout Us
Service
Case StudiesInsightsContact
Get Quote
Dynavo Labs

We design and build digital products that grow businesses — websites, mobile apps, and everything in between.

Services

  • Software Development
  • Web Development
  • Mobile Development
  • AI Development
  • Quality Assurance
  • DevOps & Cloud

Company

  • About
  • Industries
  • Technology
  • Contact

Resources

  • Insights
  • Case Studies

© 2026 dynavo Labs. All rights reserved.

Privacy PolicyTerms & Conditions
dynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labsdynavo Labs

Debounce vs Throttle with Practical Examples

The difference between debounce and throttle in JavaScript, with code for search inputs, scroll handlers and resize events, plus how to use them in React.

By Muzammil•October 31, 2026•6 min read•Software Development•Frontend
Debounce vs Throttle with Practical Examples

Some browser events fire far more often than your code needs to react. A scroll handler can run dozens of times per second. A search box fires on every keystroke. If each event triggers a network call or a heavy calculation, the page slows down and the server takes needless load.

Debounce and throttle are two ways to limit how often a function runs. They are often confused. This guide explains the difference and shows when to use each.

The Short Version

  • Debounce waits until the events stop. The function runs once, after a quiet period.
  • Throttle runs the function at a steady rate while events keep coming. At most once per interval.
DebounceThrottle
RunsAfter events stop for N msAt most once every N ms
During a burstNothing, until it endsRegularly
Number of calls for a 1-second burst, N = 200 ms1About 5
Good forFinal value mattersOngoing feedback matters

What Is Debounce?

Debounce delays a function until a set time has passed since the last call. Each new call resets the timer.

function debounce(fn, delay) {
  let timer

  const debounced = (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => fn(...args), delay)
  }

  debounced.cancel = () => clearTimeout(timer)
  return debounced
}

Typing "react" quickly produces five keystrokes. With a 300 ms debounce, the function runs once, 300 ms after the last letter.

Example: Search as You Type

const search = debounce(async (query) => {
  const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`)
  render(await res.json())
}, 300)

input.addEventListener('input', (event) => search(event.target.value.trim()))

Without the debounce, typing "react" sends five requests. With it, one.

What Is Throttle?

Throttle lets a function run at most once per interval and ignores the calls in between.

function throttle(fn, interval) {
  let last = 0

  return (...args) => {
    const now = Date.now()
    if (now - last >= interval) {
      last = now
      fn(...args)
    }
  }
}

This version runs on the first call and then at most once per interval. It drops the final call if it falls inside the interval, which matters if you need the last value. A more complete version, such as the one in Lodash, can also run on the trailing edge.

Example: Scroll Position

const updateProgress = throttle(() => {
  const scrolled = window.scrollY / (document.body.scrollHeight - window.innerHeight)
  bar.style.width = `${Math.min(scrolled, 1) * 100}%`
}, 100)

window.addEventListener('scroll', updateProgress, { passive: true })

The progress bar updates ten times per second while scrolling, which looks continuous and costs little.

Which One Should You Use?

Ask what you need during the burst.

SituationChoiceReason
Search suggestionsDebounceOnly the final text matters
Form validation as you typeDebounceValidate when typing pauses
Auto-save a draftDebounceSave after the user stops
Window resize layout recalculationDebounceCompute once at the final size
Scroll progress or sticky headersThrottleNeeds regular updates
Mouse move or drag trackingThrottleNeeds ongoing position
Infinite scroll checkThrottleCheck regularly, not on every pixel
Button double-click protectionThrottle (leading edge)Run the first click, ignore the rest

If the answer to "do I only care about the last value?" is yes, debounce. If it is "I need updates while it happens", throttle.

Using Them in React

Creating the function inside a component body makes a new debounced function on every render, and the timer state is lost. Keep one instance stable.

import { useEffect, useMemo, useState } from 'react'

function SearchBox() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])

  const search = useMemo(
    () =>
      debounce(async (text) => {
        const res = await fetch(`/api/search?q=${encodeURIComponent(text)}`)
        setResults(await res.json())
      }, 300),
    [],
  )

  useEffect(() => () => search.cancel(), [search])

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

Three details matter here:

  • useMemo with an empty dependency array keeps the same debounced function across renders.
  • The cleanup cancels a pending call when the component unmounts, which prevents a state update on an unmounted component.
  • The input stays controlled and updates instantly. Only the network call is delayed.

A second option is to debounce the value, not the function. A useDebouncedValue hook returns the value after it has been stable for N ms, and a normal useEffect reacts to it. This reads more naturally in React code.

Handling Out-of-Order Responses

Debouncing reduces requests but does not guarantee the responses arrive in order. A slow response for an older query can overwrite a newer one. Cancel the previous request with an AbortController:

let controller

const search = debounce(async (query) => {
  controller?.abort()
  controller = new AbortController()

  try {
    const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
      signal: controller.signal,
    })
    render(await res.json())
  } catch (error) {
    if (error.name !== 'AbortError') throw error
  }
}, 300)

Choosing the Delay

There is no universal number. These ranges are a starting point:

  • Debounce for typing: 250 to 400 ms. Shorter feels responsive and sends more requests.
  • Throttle for scroll: 50 to 100 ms. Longer starts to look choppy.
  • Resize: 150 to 250 ms.

For animations tied to scrolling, requestAnimationFrame is often better than a fixed interval, because it matches the screen refresh rate.

Common Mistakes

  • Creating a new debounced function on every call. Each one has its own timer, so nothing is debounced.
  • Debouncing when you need regular updates. A scroll indicator with debounce only moves after you stop scrolling.
  • Forgetting to cancel on unmount. A pending call can run after the component is gone.
  • Delays that are too long. A 1-second debounce on a search box feels broken.
  • Using either one to hide a slow function. If a handler is slow, fix it or move it to a worker.

Summary

Debounce runs a function once after activity stops. Throttle runs it regularly while activity continues. Pick debounce when only the final value matters, such as search and auto-save, and throttle when you need updates during the activity, such as scroll and drag.

#JavaScript#Debounce#Throttle#React#Performance#Web Development

Responses

M↓

Get Started on a Project?

Let's Talk

Start the
Journey
dynavolabs.info@gmail.comStart the Journey