EnriqueMark
Essays

Debouncing and race conditions

2026-02 English

Debouncing matters, especially when there’s network IO between frontend and backend. If every user action fires a request to the backend, the bandwidth bill and the load on the backend server get heavy. The standard fix is to add a delay and only take the last of the user’s consecutive actions in that window (or the final result). On debouncing, Angular’s RxJS library has operators that go with it:

// other imports omitted
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
@Component({ ... })
export class SearchComponent {
  // 1. Create a form control, which is itself an Observable source
  searchControl = new FormControl('');
  // 2. Define the result stream
  // If if an Observable is a water pipe, .pipe here can be understood as a filter
  // Things go in, get worked on a bunch, and at the end it outputs the processed result
  // valueChanges listens for value change events, and starts the work if there is one
  users$ = this.searchControl.valueChanges.pipe(
    // --- pipeline starts ---
    // note the three below are optional, their jobs overlap, no need to add them all
    
    // Debounce: if the user is still typing within 300ms, do nothing
    debounceTime(300),
    
    // Dedupe: if the user types "A" -> deletes it back to "" -> types "A" again,
    // the value hasn't changed, so don't send a request
    distinctUntilChanged(),
    
    // SwitchMap
    // Handles the race condition, as soon as a new value arrives it immediately cancels the last unfinished HTTP request
    // Only ever keeps the result of the last request
    switchMap(term => {
      // if empty, return an empty array, don't send a request
      if (!term) return []; 
      // fire a new request
      return this.http.get<User[]>(`/api/search?q=${term}`);
    })
  );

  constructor(private http: HttpClient) {}
}

A race condition, concretely, is when network jitter scrambles the load order of what the user sent, and the current request ends up overwritten by earlier data that came back later. switchMap is what you use to solve this. If the value changes, it cancels the previous request outright and only ever takes the result of the last one.


Translation note. I wrote this in Chinese. This English version is an LLM translation, so the wording is not mine even though the thinking is. Original: 防抖和竞态问题.