Combine two network sources using Coroutines and Flow

Tests your grasp of async data combination and state management. A good answer uses Flow.combine in a ViewModel, fed by a Repository, emitting a sealed class for UI state (Loading, Success, Error). A red flag is trying to combine state in the UI layer itself.
What's really being asked
This is a core modern Android architecture question. It's not just about knowing the syntax of Flow.combine; it tests your ability to design a clean, resilient, and testable data flow from the network to the UI. The interviewer is looking for: 1) Correct use of structured concurrency and coroutine scopes (viewModelScope). 2) Efficient combination of asynchronous data streams without race conditions. 3) A robust state management pattern for the UI (loading, success, partial failure, total failure). 4) A clear separation of concerns between ViewModel, Repository, and DataSource.
The full answer
A senior-level answer should walk through the layers. First, define a sealed class for the UI state, like UiState<T> with Loading, Success(data: T), and Error(message: String) variants. Second, in the ViewModel, create a private _uiState as a MutableStateFlow and expose it as a public, immutable StateFlow. Third, within an init block or a function, launch a coroutine in viewModelScope. Inside, use repository.flowA.combine(repository.flowB) { resultA, resultB -> ... }. The combine block is where you merge the results into a single data model for the UI. Fourth, use .onStart { emit(UiState.Loading) } and .catch { emit(UiState.Error(...)) } on the combined flow to handle loading and error states before collecting into your _uiState. The Repository would expose two separate Flows, each coming from a different DataSource (e.g., a Retrofit service).
The mistakes people make
A common mistake is to use two separate StateFlows in the ViewModel and try to combine them in the UI (Fragment/Activity). This couples the UI layer to business logic and is hard to manage. Another red flag is using nested launch blocks or async/await in a way that creates complex cancellation logic. Forgetting to use .catch() on the individual flows within the repository or on the combined flow is a major issue, as one failing network call would crash the entire coroutine. Suggesting zip instead of combine is also a nuanced mistake; zip waits for both sources to emit a new item together, while combine emits whenever either source emits a new value, which is usually the desired behavior for a reactive UI.
What usually comes next
How would you handle a case where one request succeeds but the other fails? (Tests state modeling). How would you add caching to this flow? (Tests repository patterns and sources of truth). How would you write a unit test for this ViewModel logic? (Tests knowledge of runTest, Turbine, and mocking). What if one flow is fast and the other is very slow; how does combine behave?
A concrete example
In the ViewModel, a concise implementation uses the stateIn operator: val uiState: StateFlow<UiState> = repository.getUserProfileFlow().combine(repository.getUserSettingsFlow()) { profile, settings -> UiState.Success(CombinedData(profile, settings)) }.onStart { emit(UiState.Loading) }.catch { emit(UiState.Error(it.message ?: "Unknown Error")) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), UiState.Loading). This single expression demonstrates combine, state wrapping, error handling, and converting the cold Flow into a hot StateFlow shared among collectors.
Interview question
What is the primary architectural benefit of using `Flow.combine` in a ViewModel, compared to exposing two separate `StateFlow`s for the UI to manage?
- a.It is the only operator that can transform two different data types into a single UI model.
- b.It ensures both network requests execute in parallel, resulting in faster data loading.
- c.It automatically provides partial data to the UI if one of the network flows fails.
- d.It centralizes state production logic, leading to a simpler, more declarative UI and easier testing.Correct
Why? this is the answer
This pattern centralizes business logic in the ViewModel, making it the single source of truth and simplifying the UI. Option C is a tempting distractor, but handling partial failures is not automatic; it requires explicit error handling logic within the flow chain.
Just read this? Test yourself on what you have been reading.
Read the original → developer.android.com
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on android — each one lists the topics its interview covers.
See open roles