As asked
You have a Kotlin app where search input emits quickly while network responses arrive slowly. How would you design the Flow pipeline?
Sample answer outline
A solid answer uses debounce to avoid firing on every keystroke, distinctUntilChanged to skip repeated queries and flatMapLatest to cancel stale searches. Network work should run on the right dispatcher and expose a sealed UI state for loading, success and error. The pipeline should be collected from a lifecycle-aware scope so it stops when the screen is gone. Candidates often use collect on every query, which lets slow responses race and overwrite newer results. Strong answers mention testing with virtual time because timing-heavy Flow code is otherwise fragile.
Reference implementation (kotlin)
val uiState: StateFlow<SearchState> =
queryFlow
.debounce(300)
.map { it.trim() }
.distinctUntilChanged()
.flatMapLatest { query ->
flow {
emit(SearchState.Loading(query))
emit(SearchState.Results(repository.search(query)))
}.catch { emit(SearchState.Error(query, it)) }
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), SearchState.Idle)Expect these follow-ups
- What is the difference between flatMapLatest and flatMapMerge here?
- How do you keep cached results visible while a refresh is loading?
- How would you test the debounce behaviour?