Flow: Cold vs Hot Streams
📖 Concept
Kotlin Flow is the reactive streams API for Kotlin coroutines. Understanding the difference between cold and hot flows is essential at the senior level.
Cold Flow (Flow
- Created with
flow { }builder - Code inside runs ONLY when a collector starts collecting
- Each collector gets its own independent emission
- Like a function — executes per call
Hot Flow (SharedFlow / StateFlow):
- Active regardless of collectors
- All collectors receive the same emissions
- Like a broadcast — anyone can tune in
- SharedFlow: no initial value, configurable replay
- StateFlow: always has a current value (like LiveData), replay of 1
StateFlow vs SharedFlow:
| Feature | StateFlow | SharedFlow |
|---|---|---|
| Initial value | Required | Not required |
| Replay | Always 1 | Configurable (0 to N) |
| Equality check | Skips duplicate values | Emits all values |
| Use case | UI state | Events, one-time triggers |
StateFlow vs LiveData:
StateFlow requires an initial value, works with coroutines natively, supports operators (map, filter), and doesn't depend on Android lifecycle awareness (use repeatOnLifecycle instead).
💻 Code Example
1// Cold Flow — executes per collector2fun getNumbers(): Flow<Int> = flow {3 println("Flow started") // Runs when collect() is called4 for (i in 1..5) {5 delay(100)6 emit(i) // Suspends until collector processes the value7 }8}910// Each collect() runs the flow independently11launch { getNumbers().collect { println("A: $it") } } // Flow started, 1,2,3,4,512launch { getNumbers().collect { println("B: $it") } } // Flow started, 1,2,3,4,51314// Hot Flow — StateFlow for UI state15@HiltViewModel16class SearchViewModel @Inject constructor(17 private val repository: SearchRepository18) : ViewModel() {1920 private val _query = MutableStateFlow("")2122 val searchResults: StateFlow<SearchUiState> = _query23 .debounce(300) // Wait 300ms after last keystroke24 .distinctUntilChanged() // Skip if same query25 .flatMapLatest { query -> // Cancel previous search26 if (query.isEmpty()) flowOf(SearchUiState.Empty)27 else repository.search(query)28 .map { SearchUiState.Results(it) as SearchUiState }29 .onStart { emit(SearchUiState.Loading) }30 .catch { emit(SearchUiState.Error(it.message ?: "")) }31 }32 .stateIn(33 scope = viewModelScope,34 started = SharingStarted.WhileSubscribed(5000),35 initialValue = SearchUiState.Empty36 )3738 fun onQueryChanged(query: String) { _query.value = query }39}4041// SharedFlow — for one-time events (navigation, snackbar)42class EventViewModel : ViewModel() {43 private val _events = MutableSharedFlow<UiEvent>()44 val events: SharedFlow<UiEvent> = _events.asSharedFlow()4546 fun onSaveClicked() {47 viewModelScope.launch {48 try {49 repository.save()50 _events.emit(UiEvent.ShowSnackbar("Saved!"))51 _events.emit(UiEvent.NavigateBack)52 } catch (e: Exception) {53 _events.emit(UiEvent.ShowSnackbar("Error: ${e.message}"))54 }55 }56 }57}5859// Collecting in Compose — lifecycle-aware60@Composable61fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {62 val state by viewModel.searchResults.collectAsStateWithLifecycle()63 // collectAsStateWithLifecycle automatically handles lifecycle64}6566// Collecting in Activity/Fragment67lifecycleScope.launch {68 repeatOnLifecycle(Lifecycle.State.STARTED) {69 viewModel.searchResults.collect { state -> updateUI(state) }70 }71}
🏋️ Practice Exercise
Practice:
- Implement a search feature with debounce using StateFlow and flatMapLatest
- Create a SharedFlow-based event system for one-time UI events
- Convert a callback-based API to a Flow using callbackFlow
- Explain when to use SharingStarted.WhileSubscribed vs SharingStarted.Eagerly
- Implement a Flow that combines data from two sources using combine()
⚠️ Common Mistakes
Using StateFlow for one-time events (navigation, snackbar) — StateFlow replays the last value on new collectors, causing duplicate events
Not using collectAsStateWithLifecycle in Compose — collect() doesn't respect lifecycle, wastes resources when app is backgrounded
Using SharedFlow with replay=1 when StateFlow is better — StateFlow has built-in conflation and equality checks
Not handling backpressure in hot flows — fast emitter + slow collector can cause memory issues
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Flow: Cold vs Hot Streams. Login to unlock this feature.