Debouncing in JavaScript
“Sometimes, moving fast means slowing things down a little.”
The Day My Search Bar Went Rogue
A while back, I built a search box for a project. I typed:
c
→ ca
→ car
→ cars
And guess what?
My app sent 4 separate requests to the server… for one word.
The page slowed down, the backend guy gave me the “what did you do?” look…
and that’s when I learned about debouncing.
What is Debouncing, Really?
Debouncing is just a smart delay.
It waits until you’re done doing something, then runs the action.
Imagine you’re writing on a whiteboard, and your friend says:
“I’ll take a picture when you stop writing for 1 second.”
If you keep writing, they wait longer.
That’s debouncing — no rush, just waiting for you to finish.
Why debouncing ?
Without debouncing:
- Your browser does too much work.
- Every letter typed by user, triggers a request to server
- Your server gets spammed.
- Your users feel lag.
With debouncing:
- Your app feels smooth.
- You save API calls.
Common places we use it:
- Search boxes (wait till typing stops before searching)
- Scroll events (lazy loading images)
- Window resizing (adjust layout only after resizing ends)
- Buttons (avoid double form submissions)
How It Works
- You wrap your function inside a debounce utility.
- Every time it’s triggered, the timer resets.
- If the timer finishes, the function finally runs.
Basic Debounce Function
function debounce(func, delay) {
let timer;
return function (...args) {
clearTimeout(timer); // Stop any ongoing timer
timer = setTimeout(() => {
func.apply(this, args); // Run the function where API call happening finally
}, delay);
};
}
Example: Search Box With Debouncing
const searchBox = document.getElementById("search");
function fetchResults(query) {
console.log("Fetching results for:", query);
}
const debouncedSearch = debounce(fetchResults, 500);
searchBox.addEventListener("input", (e) => {
debouncedSearch(e.target.value);
});
Now the search only runs once after you pause typing for 500ms.
Leading & Trailing Edge
- Trailing edge (default) → Runs after you stop typing.
- Leading edge → Runs immediately on the first action, then waits.
Example with lodash:
import debounce from "lodash.debounce";
window.addEventListener(
"resize",
debounce(() => {
console.log("Window resized!");
}, 300, { leading: true, trailing: false })
);
Debouncing in React
Great for search boxes, API calls, or expensive operations.
import React, { useState, useCallback } from "react";
import debounce from "lodash.debounce";
export default function SearchBar() {
const [query, setQuery] = useState("");
const debouncedSearch = useCallback(
debounce((value) => {
console.log("Searching for:", value);
}, 500),
[]
);
return (
<inputvalue={query}
onChange={(e) => {
setQuery(e.target.value);
debouncedSearch(e.target.value);
}}
placeholder="Search..."
/>
);
}
Common Mistakes
- Setting delay too short → No real benefit.
- Setting delay too long → Feels slow for users.
- Forgetting to clear timers in React → Memory leaks.
- Confusing debounce with throttle.(both are interlinked but not the same)
Final Takeaway
Debouncing is like saying:
“Let’s wait till you’re done giving command before sending the execution request.” e.g. We decide all the dishes we want to order before finally calling the waiter.(We should not simply call the waiter 5 times for 5 different dishes)
It’s a tiny change in code, but it makes your app feel faster, smoother, and lighter.
Next time your app feels slow, use debounce and chill!!!