{"post":{"slug":"how-to-debounce-a-function-2026","title":"How to debounce a function in 2026","excerpt":"Debouncing delays a function call until a burst of rapid triggers settles, preventing runaway API calls and laggy UIs. Here is the pattern and when to use it.","category":"code","date":"March 30, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">D</span>ebouncing is one of those patterns you implement once and then never think about again — until you see a developer fire an API call on every keystroke and wonder why the server is melting. The concept is simple: delay execution until a burst of calls quiets down. The implementation is ten lines. Getting it right in React needs a bit more care.\n\n## What changed in 2026\n\n- **`use-debounce` is the standard React hook** — it handles the `useRef` stabilization for you and has TypeScript types built in.\n- **Native `scheduler` API is maturing** — the browser Scheduler API offers priority-based scheduling, but for input debouncing the classic `setTimeout` approach is still the right tool.\n- **React 19 `useDeferredValue`** handles the UI update side of debouncing for React-managed state, but does not prevent API calls — you still need a debounce for network requests.\n- **Server Components reduce the need for client-side debounce** — if the search is a server action, you debounce the submit, not the render.\n\n## The plain JavaScript implementation\n\n```javascript\nfunction debounce(fn, delay) {\n  let timer;\n  return function (...args) {\n    clearTimeout(timer);\n    timer = setTimeout(() => fn.apply(this, args), delay);\n  };\n}\n\n// Usage\nconst onSearch = debounce((query) => {\n  console.log(\"searching for\", query);\n}, 300);\n\ndocument.querySelector(\"#search\").addEventListener(\"input\", (e) => {\n  onSearch(e.target.value);\n});\n```\n\nEvery call resets the timer. The wrapped function only executes once there is a 300 ms gap in calls.\n\n## TypeScript version\n\n```typescript\nfunction debounce<T extends (...args: unknown[]) => void>(\n  fn: T,\n  delay: number\n): (...args: Parameters<T>) => void {\n  let timer: ReturnType<typeof setTimeout>;\n  return (...args: Parameters<T>) => {\n    clearTimeout(timer);\n    timer = setTimeout(() => fn(...args), delay);\n  };\n}\n```\n\n## React — the wrong way\n\n```tsx\n// Bad: creates a new debounced function on every render\nfunction SearchInput() {\n  const handleChange = debounce((e) => {\n    fetchResults(e.target.value);\n  }, 300);\n\n  return <input onChange={handleChange} />;\n}\n```\n\nEvery render discards the previous debounced function and creates a fresh one — the timer never accumulates the silence it needs.\n\n## React — the right way with useRef\n\n```tsx\nimport { useRef, useCallback } from \"react\";\n\nfunction SearchInput() {\n  const timerRef = useRef<ReturnType<typeof setTimeout>>();\n\n  const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n    clearTimeout(timerRef.current);\n    timerRef.current = setTimeout(() => {\n      fetchResults(e.target.value);\n    }, 300);\n  }, []); // stable reference — no deps\n\n  return <input onChange={handleChange} />;\n}\n```\n\n## React — the easiest way with use-debounce\n\n```bash\nnpm install use-debounce\n```\n\n```tsx\nimport { useDebouncedCallback } from \"use-debounce\";\n\nfunction SearchInput() {\n  const handleChange = useDebouncedCallback((value: string) => {\n    fetchResults(value);\n  }, 300);\n\n  return <input onChange={(e) => handleChange(e.target.value)} />;\n}\n```\n\n`useDebouncedCallback` returns a stable function — no `useRef` boilerplate needed.\n\n## Debounce a React state value (for UI)\n\n```tsx\nimport { useState } from \"react\";\nimport { useDebounce } from \"use-debounce\";\n\nfunction SearchPage() {\n  const [input, setInput] = useState(\"\");\n  const [query] = useDebounce(input, 300);\n\n  // query updates 300ms after the user stops typing\n  // use query to drive your data fetching\n\n  return (\n    <>\n      <input value={input} onChange={(e) => setInput(e.target.value)} />\n      <Results query={query} />\n    </>\n  );\n}\n```\n\nThis pattern keeps the input responsive (updates on every keystroke) while deferring the expensive downstream effect.\n\n## Debounce vs throttle\n\n| Pattern | Fires when | Use case |\n| --- | --- | --- |\n| Debounce | After N ms of silence | Search, auto-save, resize-end |\n| Throttle | At most once per N ms | Scroll handlers, analytics events |\n| Leading debounce | Immediately, then silent for N ms | Button click de-duplication |\n| useDeferredValue | React decides (concurrent) | Keeping input fast, deferring list render |\n\n## How to pick the delay\n\n| Use case | Typical delay |\n| --- | --- |\n| Search / autocomplete | 250–350 ms |\n| Form auto-save | 800–1 500 ms |\n| Window resize handler | 100–200 ms |\n| Button double-click guard | 300–500 ms |\n| Scroll position updates | 50–100 ms (throttle is better here) |\n\n## Common mistakes\n\n**Debouncing inside the render function.** Explained above — the function is recreated and the timer is lost.\n\n**Not cleaning up the timer on unmount.** If the component unmounts while the timer is pending, the callback fires on an unmounted component. Add a cleanup in `useEffect` if you build this manually.\n\n**Using debounce when you mean throttle.** A scroll handler debounced at 200 ms fires nothing while the user is scrolling, then fires once they stop. Usually you want periodic updates while scrolling — that is throttle.\n\n**Setting delay too high on search.** A 1 000 ms debounce on a search input feels broken. 250–350 ms is the sweet spot for typing speed.\n\n## What to skip\n\n- **Lodash just for debounce** — `lodash.debounce` standalone is 6 kB minzipped. Write the 10-line version or use `use-debounce` (< 1 kB).\n- **Debouncing server actions in Next.js App Router** — Next.js already batches transitions; debounce the user input, not the server action call.\n- **RxJS `debounceTime` for non-reactive codebases** — huge dependency for one operator.\n\n## FAQ\n\n**Can I cancel a pending debounced call?**\nYes. The `use-debounce` `useDebouncedCallback` returns a function with a `.cancel()` method. Call it in a `useEffect` cleanup: `return () => handleChange.cancel()`.\n\n**How is debounce different from useDeferredValue?**\n`useDeferredValue` is a React scheduling hint — it keeps the current value for the urgent render and schedules a lower-priority render with the new value. It does not delay network calls. Use both: `useDeferredValue` for UI smoothness, debounce for network requests.\n\n**Should I debounce or use AbortController?**\nBoth together. Debounce reduces the number of requests fired; AbortController cancels in-flight requests that are superseded.\n\n**Does debounce work with async functions?**\nYes, but the timer callback just fires the async function — it does not await it. The returned promise is discarded. Use AbortController inside the async function to cancel previous in-flight calls.\n\n## Where to go next\n\n- [How to throttle requests in 2026](/blog/how-to-throttle-requests-2026)\n- [How to optimize React performance in 2026](/blog/how-to-optimize-react-performance-2026)\n- [How to handle API errors in 2026](/blog/how-to-handle-api-errors-2026)","hue":318,"metaTitle":"How to Debounce a Function in 2026 (JS, React, TypeScript)","metaDescription":"Learn how to debounce a function in JavaScript and React in 2026. Covers the raw implementation, use-debounce hook, and common pitfalls to avoid.","keywords":["how to debounce a function 2026","javascript debounce","react debounce hook","use-debounce react","debounce vs throttle 2026","debounce typescript","debounce search input react","usedebouncedcallback","debounce form autosave","frontend performance debounce"],"tldr":["**Debounce delays the call until N ms of silence** — ideal for search inputs, resize handlers, and form auto-save.","**Throttle fires at most once every N ms** — use it when you need periodic updates, not just the final one.","**Write the 10-line version yourself or use use-debounce** — lodash.debounce is 6 kB for one function; the lighter packages or a custom hook are fine.","**In React, stabilize the debounced function with useRef** — creating it inside the component body recreates it on every render, defeating the debounce.","**Skip:** debouncing with setTimeout directly in an event handler — you leak timers and lose the ability to cancel."],"readTime":"5 min read","wordCount":998},"related":[{"slug":"what-is-debouncing-vs-throttling-2026","title":"Debounce vs Throttle in 2026: Which One to Use","excerpt":"Debounce and throttle both limit how often a function runs, but they do it differently and fit different events. Here is how to tell them apart in 2026 and pick the right one.","category":"code","date":"July 22, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":310},{"slug":"how-to-throttle-requests-2026","title":"How to throttle requests in 2026","excerpt":"Throttling limits how often a function or HTTP request fires, protecting APIs and keeping UIs smooth. This covers client-side throttle, rate limiting, and AbortController.","category":"code","date":"April 4, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":319},{"slug":"react-vs-angular-2026","title":"React vs Angular in 2026: which framework is right for your team?","excerpt":"React and Angular take opposite bets on structure and flexibility. This 2026 guide cuts through the hype to tell you which wins for your project size and team.","category":"code","date":"April 7, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":309}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-dca-into-index-funds-2026","title":"How to DCA into index funds in 2026: the complete setup guide","excerpt":"Dollar-cost averaging removes the hardest part of investing: deciding when to buy. Here is how to set up automatic DCA into index funds in 2026.","category":"finance","date":"March 30, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":298},"older":{"slug":"how-to-debug-a-memory-leak-2026","title":"How to debug a memory leak in 2026: tools, patterns, and fixes","excerpt":"Memory leaks kill long-running services slowly and invisibly. This guide covers how to detect, profile, and fix leaks in Node.js, Python, and Go in 2026.","category":"code","date":"March 30, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":315}}