How would you architect state-driven UI with ViewModel and Compose?

Tests unidirectional data flow and state hoisting. Model screen state as an immutable data class, expose StateFlow from ViewModel, and collect it in Compose so events flow up and state down. Red flag: exposing mutable state or putting logic in Composables.
What's really being asked
The interviewer wants to see if you treat the ViewModel as the single source of truth and keep Composables as pure functions of state. They care about unidirectional data flow, immutability, recomposition performance, and separation of concerns. Specifically, they are checking whether you know how to model UI state as a single coherent object rather than a scattered collection of variables, and whether you understand the lifecycle boundaries between Compose and ViewModel.
The full answer
First, model the screen state as an immutable data class that contains everything the UI needs to render, including loading flags, data lists, and error messages. Second, hold that state in a private MutableStateFlow inside the ViewModel and expose it as a public read-only StateFlow. Third, in the Composable, collect that flow with collectAsStateWithLifecycle to respect the lifecycle and avoid unnecessary recompositions. Fourth, send user actions back to the ViewModel via callback lambdas or a sealed class event sink so that all business logic lives above the UI layer. Fifth, keep Composables stateless and idempotent by deriving all presentation values from the state object rather than using remember for business data.
The mistakes people make
Exposing mutable state such as var uiState directly from the ViewModel breaks the contract and allows Composables to mutate state. Using multiple LiveData or StateFlow objects for different parts of the screen creates synchronization bugs and makes loading or error handling inconsistent. Putting business logic like network calls or validation inside Composables makes them untestable and couples them to the Android framework. Using rememberSaveable for screen-level state that should survive process death is a mistake when the ViewModel with SavedStateHandle already owns that responsibility.
What usually comes next
How do you handle partial state updates without triggering full recompositions? When would you use CompositionLocal instead of passing state down through parameters? How do you debounce rapid user inputs before they reach the ViewModel? What is the difference between collectAsState and collectAsStateWithLifecycle, and why does it matter for configuration changes?
A concrete example
Imagine a news feed screen. The ViewModel holds a private val _uiState = MutableStateFlow(NewsFeedUiState()) and exposes val uiState: StateFlow<NewsFeedUiState> = _uiState.asStateFlow(). The Composable calls val state by viewModel.uiState.collectAsStateWithLifecycle() and renders a list when state.articles is non-empty, a loading spinner when state.isLoading is true, and an error banner when state.error is non-null. Tapping a bookmark button invokes viewModel.onBookmarkClicked(articleId), which updates the flow internally. The Composable never writes to the state.
Interview question
When architecting a news feed screen with ViewModel and Compose, which approach best prevents synchronization bugs between loading indicators and data lists?
- a.Model screen state as a single immutable data class backed by one StateFlow, collected with collectAsStateWithLifecycleCorrect
- b.Expose separate StateFlows for isLoading and articles, collecting each independently in the Composable
- c.Hold articles as a mutable var in the ViewModel and mutate it directly from bookmark click handlers
- d.Use rememberSaveable in the Composable to cache the articles list across configuration changes
Why? this is the answer
A single immutable state object atomically bundles all screen properties so the UI can never observe a torn state where loading is true but data is stale. Option B is tempting because separate flows feel modular, yet they allow independent updates that desynchronize loading indicators from content.
Just read this? Test yourself on what you have been reading.
Read the original → developer.android.com
- #android
- #jetpack-compose
- #viewmodel
- #state-management
- #unidirectional-data-flow
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