The Problem
Rendering large datasets — like 1500+ city names in an autocomplete — can seriously affect performance. Without any optimization, each keystroke causes filtering + re-rendering, leading to lag and janky UX.
The Solution: Debounced Client-side Search
Instead of triggering filtering on every keystroke, debounce the input. This way, filtering is only triggered after the user stops typing for a brief moment — leading to smoother UI and less CPU usage.
What Is Debouncing?
Debouncing delays a function’s execution until a certain time has passed since its last invocation. It’s useful for expensive operations triggered by rapid-fire events.
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
Or simply use lodash:
import debounce from 'lodash.debounce';
How I Used It in My Project
I had an input field that allowed users to search through 1500+ city names. Here’s the straightforward solution I implemented:
const [searchTerm, setSearchTerm] = useState('');
const [filteredCities, setFilteredCities] = useState([]);
const handleSearch = debounce((value) => {
const results = cities.filter(city =>
city.toLowerCase().includes(value.toLowerCase())
);
setFilteredCities(results);
}, 300);
const handleChange = (e) => {
const value = e.target.value;
setSearchTerm(value);
handleSearch(value);
};
return (
);
This approach gave me:
- Instant responsiveness while typing
- Zero jank, even with 1500+ entries
- No need to bring in heavy libraries
Why This Is Effective
This method shines when:
- The list is large but manageable in memory
- You don’t need to paginate or scroll huge chunks of DOM
- Users expect fast results as they type
When to Use Debounced Search
Use this when:
- You have a list that can be filtered entirely in memory
- You want to prevent excessive computations on every keystroke
- Your primary goal is input smoothness, not virtual rendering
Final Thoughts
Don’t overengineer. For many use cases, client-side filtering + debounced input is all you need. It’s a simple, powerful trick to keep your app snappy and clean.
Give it a try in your next search input — your users (and your performance metrics) will thank you.
Full Code Snippet (Standalone Component)
import React, { useState } from 'react';
import debounce from 'lodash.debounce';
const CitySearch = ({ cities }) => {
const [searchTerm, setSearchTerm] = useState('');
const [filteredCities, setFilteredCities] = useState([]);
const handleSearch = debounce((value) => {
const results = cities.filter(city =>
city.toLowerCase().includes(value.toLowerCase())
);
setFilteredCities(results);
}, 300);
const handleChange = (e) => {
const value = e.target.value;
setSearchTerm(value);
handleSearch(value);
};
return (
{filteredCities.map((city, index) => (
{city}
))}
);
};
export default CitySearch;