Flutter Search Debounce: A 10-Line Timer Pattern
Flutter search debounce in 10 lines: stop firing a network request on every keystroke — wait until the user pauses typing, then search once.
Type "flutter" into a search field wired to onChanged and you fire 7 network requests — one per keystroke — for a query the user never intended to search. Six of them are wasted work: wasted bandwidth, wasted server CPU, and results flashing on screen for queries the user already deleted. This Flutter search debounce pattern fixes it in about ten lines: wait until the user pauses typing, then search once. The problem The naive implementation looks innocent: TextField( onChanged: (query) { searchApi(query); // Fires on EVERY keystroke. }, ) Type "dart" and your app requests d , then da , then dar , then dart . The responses can also arrive out of order — the slow request for d lands after the fast one for dart and overwrites the correct results. This is one of the cheapest UX wins in Flutter: a tiny change with a visible payoff. What debouncing is (and what it isn't) Debounce: wait until the input has been quiet for X milliseconds, then act once. Typing continuously resets the clock; only the final pause triggers the work. Throttle: allow at most one action per X milliseconds, firing on a fixed cadence while input continues. Debounce is what you want for search — you care about the settled query, not intermediate states. Throttle fits continuous streams like scroll position or window resize. Don't mix them up: throttling a search field still fires mid-typing requests, which is exactly what you're trying to avoid. Step 1 — The Flutter search debounce Timer pattern The whole technique is one Timer? field and three lines of logic: import 'dart:async'; Timer? _debounce; void onSearchChanged(String query) { // Each keystroke cancels the pending search... _debounce?.cancel(); // ...and schedules a new one 500ms out. Only the last // keystroke's timer survives long enough to…