# De 20 meest gestelde Jetpack Compose interviewvragen in 2026 > De 20 meest gestelde Jetpack Compose interviewvragen: recomposition, state management, navigatie, performance en architectuurpatronen. - Published: 2026-01-27 - Updated: 2026-04-12 - Author: SharpSkill - Tags: jetpack compose, android, interview, kotlin, ui - Reading time: 14 min --- Jetpack Compose is uitgegroeid tot de standaard UI-toolkit voor Android-ontwikkeling. Technische sollicitatiegesprekken testen inmiddels routinematig Compose-vaardigheden — van recomposition-mechanismen tot state management en performance-optimalisatie. Hier zijn de 20 meest gestelde vragen met uitgebreide antwoorden en codevoorbeelden. > **Hoe deze gids te gebruiken** > > Elke vraag bevat een gestructureerd antwoord en een codevoorbeeld. De vragen zijn geordend op oplopende moeilijkheidsgraad: basis, gemiddeld, dan gevorderd. ## Jetpack Compose basisprincipes ### 1. Wat is het verschil tussen Compose en het XML-view-systeem? Compose gebruikt een **declaratief** paradigma: de UI wordt beschreven als een functie van de state, en het framework handelt updates automatisch af. Het traditionele XML-systeem is **imperatief** — views moeten handmatig worden gemanipuleerd via `findViewById` of View Binding. ```kotlin // DeclarativeExample.kt // Compose: UI updates automatically when count changes @Composable fun Counter() { var count by remember { mutableStateOf(0) } // Reactive state Button(onClick = { count++ }) { // UI declaration Text("Clicks: $count") // Recomposed automatically } } ``` Met Compose is het niet meer nodig om een `TextView`-referentie op te zoeken en handmatig bij te werken — recomposition regelt alles. ### 2. Wat is recomposition? Recomposition is het proces waarbij Compose `@Composable`-functies opnieuw aanroept wanneer hun state verandert. Alleen functies waarvan de parameters zijn gewijzigd, worden opnieuw uitgevoerd, wat de performance optimaliseert. ```kotlin // RecompositionExample.kt @Composable fun UserCard(name: String, age: Int) { Column { Text("Name: $name") // Recomposed only if name changes Text("Age: $age") // Recomposed only if age changes StaticBadge() // Not recomposed if its inputs remain the same } } @Composable fun StaticBadge() { Text("Static badge") // Compose knows this function is stable } ``` Belangrijk punt voor interviews: recomposition is **optimistisch** (Compose gaat ervan uit dat het geannuleerd kan worden) en **ongeordend** (de uitvoeringsvolgorde van composables is niet gegarandeerd). ### 3. Wat doet `remember`? `remember` bewaart een waarde over recompositions heen. Zonder `remember` zou elke recomposition de variabele terugzetten naar de initiële waarde. ```kotlin // RememberExample.kt @Composable fun InputField() { // ✅ Value survives recompositions var text by remember { mutableStateOf("") } // ❌ Without remember, text resets to "" on every recomposition // var text by mutableStateOf("") TextField( value = text, onValueChange = { text = it }, // Triggers recomposition label = { Text("Enter text") } ) } ``` ### 4. Wat is het verschil tussen `remember` en `rememberSaveable`? `remember` bewaart waarden over recompositions heen, maar verliest ze bij configuratiewijzigingen (schermrotatie). `rememberSaveable` persisteert waarden ook door configuratiewijzigingen heen via het `SavedInstanceState`-mechanisme. ```kotlin // RememberSaveableExample.kt @Composable fun SearchBar() { // Lost after screen rotation var query by remember { mutableStateOf("") } // Preserved after screen rotation var savedQuery by rememberSaveable { mutableStateOf("") } TextField( value = savedQuery, onValueChange = { savedQuery = it }, placeholder = { Text("Search...") } ) } ``` ## State Management in Compose ### 5. Wat is state hoisting? State hoisting betekent het verplaatsen van de state van een composable naar het bovenliggende element. De child-composable wordt **stateless**: deze ontvangt de state als parameters en meldt wijzigingen via callbacks. ```kotlin // StateHoistingExample.kt // ✅ Stateless composable — easy to test and reuse @Composable fun EmailInput( email: String, // State provided by parent onEmailChange: (String) -> Unit, // Callback to parent modifier: Modifier = Modifier ) { TextField( value = email, onValueChange = onEmailChange, label = { Text("Email") }, modifier = modifier ) } // Parent manages the state @Composable fun LoginForm() { var email by remember { mutableStateOf("") } EmailInput( email = email, onEmailChange = { email = it } // Parent controls state ) } ``` Dit patroon is fundamenteel in Compose en komt regelmatig terug in interviews. ### 6. Hoe werkt `derivedStateOf`? `derivedStateOf` creëert een afgeleide state die alleen recomposition triggert wanneer het berekeningsresultaat daadwerkelijk verandert — niet bij elke wijziging van de bron. ```kotlin // DerivedStateExample.kt @Composable fun FilteredList(items: List) { var searchQuery by remember { mutableStateOf("") } // Recalculated only when the filtered result actually changes val filteredItems by remember(items) { derivedStateOf { items.filter { it.contains(searchQuery, ignoreCase = true) } } } Column { TextField(value = searchQuery, onValueChange = { searchQuery = it }) LazyColumn { items(filteredItems) { item -> Text(item) } } } } ``` > **Wanneer derivedStateOf gebruiken** > > Dit mechanisme is handig wanneer een state vaak verandert maar het afgeleide resultaat zelden wijzigt (bijv. een gefilterde lijst, een knop die in- of uitgeschakeld is op basis van formuliervalidatie). ### 7. Wat is het verschil tussen `StateFlow` en Compose `State`? `StateFlow` (Kotlin Coroutines) is een reactieve stream vanuit het ViewModel. `State` is het native mechanisme van Compose om recomposition te triggeren. In de praktijk wordt `StateFlow` in een composable verzameld via `collectAsStateWithLifecycle()`. ```kotlin // StateFlowExample.kt class UserViewModel : ViewModel() { private val _uiState = MutableStateFlow(UserUiState()) val uiState: StateFlow = _uiState.asStateFlow() } @Composable fun UserScreen(viewModel: UserViewModel = viewModel()) { // Converts StateFlow to State for Compose val uiState by viewModel.uiState.collectAsStateWithLifecycle() Text("Hello, ${uiState.userName}") } ``` De aanbeveling is om `collectAsStateWithLifecycle()` te gebruiken (in plaats van `collectAsState()`), omdat het de lifecycle respecteert en het verzamelen stopt wanneer het scherm niet meer zichtbaar is. ## Side Effects en Lifecycle ### 8. Wat zijn de belangrijkste side effects in Compose? Side effects maken het mogelijk om niet-composable code (netwerkoproepen, logging, navigatie) op een gecontroleerde manier uit te voeren. De drie belangrijkste: ```kotlin // SideEffectsExample.kt @Composable fun AnalyticsScreen(screenName: String) { // LaunchedEffect: runs once when screenName changes LaunchedEffect(screenName) { analyticsTracker.logScreenView(screenName) // Suspended call } // DisposableEffect: with cleanup (like useEffect with cleanup) DisposableEffect(Unit) { val listener = onScrollListener() scrollView.addListener(listener) onDispose { scrollView.removeListener(listener) // Cleanup guaranteed } } // SideEffect: runs after every successful recomposition SideEffect { logger.log("Screen recomposed") // Non-suspended code } } ``` ### 9. Wanneer `LaunchedEffect` vs `rememberCoroutineScope` gebruiken? `LaunchedEffect` is gekoppeld aan de composition: de coroutine wordt geannuleerd wanneer de composable de composition verlaat of wanneer de key wijzigt. `rememberCoroutineScope` biedt een door de gebruiker gecontroleerde scope, handig voor door de gebruiker geïnitieerde acties (knoppen). ```kotlin // CoroutineScopeExample.kt @Composable fun DataScreen(userId: String) { // ✅ LaunchedEffect: automatic loading tied to lifecycle LaunchedEffect(userId) { loadUserData(userId) // Re-launched if userId changes } // ✅ rememberCoroutineScope: one-off user action val scope = rememberCoroutineScope() Button(onClick = { scope.launch { refreshData() } // Triggered manually }) { Text("Refresh") } } ``` ## Layouts en geavanceerde componenten ### 10. Hoe werkt `LazyColumn` en hoe verschilt het van `RecyclerView`? `LazyColumn` is het Compose-equivalent van `RecyclerView`. Het composeert alleen de elementen die zichtbaar zijn op het scherm en recyclet composables die uit het zichtbare venster scrollen. ```kotlin // LazyColumnExample.kt @Composable fun UserList(users: List) { LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) // Spacing between items ) { items( items = users, key = { it.id } // Stable key to optimize recompositions ) { user -> UserCard(user) } } } ``` Belangrijk punt: geef altijd een stabiele `key`-parameter mee om onnodige recompositions te voorkomen bij het sorteren of verwijderen van elementen. ### 11. Hoe maak je een aangepaste layout? Compose maakt het mogelijk om aangepaste layouts te creëren via de `Layout`-functie. Dit vervangt aangepaste `ViewGroup`-implementaties uit het view-systeem. ```kotlin // CustomLayoutExample.kt @Composable fun OverlappingRow( overlapOffset: Dp = (-16).dp, // Negative offset for overlap content: @Composable () -> Unit ) { Layout(content = content) { measurables, constraints -> val placeables = measurables.map { it.measure(constraints) } val width = placeables.sumOf { it.width } + (overlapOffset.roundToPx() * (placeables.size - 1)) val height = placeables.maxOf { it.height } layout(width, height) { var xOffset = 0 placeables.forEach { placeable -> placeable.placeRelative(xOffset, 0) xOffset += placeable.width + overlapOffset.roundToPx() } } } } ``` ### 12. Hoe implementeer je aangepaste theming met `MaterialTheme`? Theming in Compose is gebaseerd op `CompositionLocal`. `MaterialTheme` biedt kleur-, typografie- en vormwaarden die toegankelijk zijn in de gehele composable-boom. ```kotlin // CustomThemeExample.kt // Custom color definitions private val DarkColorScheme = darkColorScheme( primary = Color(0xFF6200EE), secondary = Color(0xFF03DAC6), background = Color(0xFF121212) ) @Composable fun AppTheme(content: @Composable () -> Unit) { MaterialTheme( colorScheme = DarkColorScheme, typography = AppTypography, // Custom typography content = content ) } // Usage in a composable @Composable fun ThemedCard() { Card(colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface // Theme access )) { Text( text = "Content", style = MaterialTheme.typography.bodyLarge // Theme typography ) } } ``` ## Navigatie in Compose ### 13. Hoe werkt Compose Navigation? Compose Navigation gebruikt een `NavHost` met routes gedeclareerd als strings (of serialiseerbare typen sinds Navigation 2.8+). ```kotlin // NavigationExample.kt @Composable fun AppNavigation() { val navController = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { HomeScreen(onNavigateToDetail = { id -> navController.navigate("detail/$id") // Navigation with argument }) } composable( route = "detail/{userId}", arguments = listOf(navArgument("userId") { type = NavType.StringType }) ) { backStackEntry -> val userId = backStackEntry.arguments?.getString("userId") ?: "" DetailScreen(userId = userId) } } } ``` ### 14. Hoe geef je data door tussen schermen? Eenvoudige argumenten (String, Int) worden direct via de route doorgegeven. Voor complexe objecten is de huidige aanbeveling om een gedeeld `ViewModel` te gebruiken of alleen een identifier door te geven en de data in het bestemmingsscherm te laden. ```kotlin // NavigationArgsExample.kt // Type-safe navigation with Kotlin Serialization (Navigation 2.8+) @Serializable data class ProfileRoute(val userId: String, val tab: String = "info") // Declaration composable { backStackEntry -> val route = backStackEntry.toRoute() ProfileScreen(userId = route.userId, tab = route.tab) } // Navigation navController.navigate(ProfileRoute(userId = "123", tab = "stats")) ``` > **Anti-patroon om te vermijden** > > Geef nooit complexe geserialiseerde objecten door in de route. Geef een ID door en laat het bestemmingsscherm de data laden via het ViewModel. ## Performance en optimalisatie ### 15. Hoe voorkom je onnodige recompositions? Drie hoofdstrategieën om onnodige recompositions te minimaliseren: ```kotlin // PerformanceExample.kt // 1. Use stable classes (data class with immutable properties) @Stable // Tells Compose this class is stable data class UserState( val name: String, val avatar: String ) // 2. Extract lambdas with remember @Composable fun OptimizedList(onItemClick: (String) -> Unit) { val stableCallback = remember(onItemClick) { onItemClick } LazyColumn { items(100) { index -> ItemRow(onClick = { stableCallback("item_$index") }) } } } // 3. Use key() to help Compose identify elements @Composable fun UserTabs(users: List) { Column { users.forEach { user -> key(user.id) { // Stable identity UserRow(user) } } } } ``` ### 16. Hoe profileer je de performance van een Compose-app? De Layout Inspector in Android Studio toont recomposition-tellers per composable. De `debugInspectorInfo`-vlag en de `CompositionTracer` helpen bij diagnostiek. ```kotlin // ProfilingExample.kt // Enable recomposition counters in debug @Composable fun DebugRecomposition(tag: String, content: @Composable () -> Unit) { val recompositionCount = remember { mutableIntStateOf(0) } SideEffect { recompositionCount.intValue++ // Incremented on every recomposition Log.d("Recomposition", "$tag: ${recompositionCount.intValue} times") } content() } // Usage DebugRecomposition("UserCard") { UserCard(user) } ``` Daarnaast genereren de **Compose Compiler Metrics** een gedetailleerd rapport over skipbare, herstartbare functies en stabiele/instabiele klassen. ### 17. Wat is de `Modifier` en waarom is deze belangrijk? `Modifier` is een geordende keten van instructies die het uiterlijk en gedrag van een composable wijzigt. De volgorde van modifiers heeft direct invloed op de rendering. ```kotlin // ModifierOrderExample.kt @Composable fun ModifierOrderDemo() { // ❌ Padding THEN background = padding not colored Text( text = "Hello", modifier = Modifier .padding(16.dp) .background(Color.Red) ) // ✅ Background THEN padding = padding is colored Text( text = "Hello", modifier = Modifier .background(Color.Red) .padding(16.dp) ) } ``` Best practice: accepteer altijd een `modifier: Modifier = Modifier`-parameter in herbruikbare composables om aanpassing door het bovenliggende element mogelijk te maken. ## Architectuur en geavanceerde patronen ### 18. Hoe structureer je een Compose-scherm met een ViewModel? Het aanbevolen patroon scheidt de UI-state in een `data class`, events in een `sealed interface`, en het ViewModel verzorgt de businesslogica. ```kotlin // ScreenArchitectureExample.kt // UI State data class ProfileUiState( val user: User? = null, val isLoading: Boolean = false, val error: String? = null ) // User events sealed interface ProfileEvent { data object Refresh : ProfileEvent data class UpdateName(val name: String) : ProfileEvent } // ViewModel class ProfileViewModel(private val repo: UserRepository) : ViewModel() { private val _uiState = MutableStateFlow(ProfileUiState(isLoading = true)) val uiState = _uiState.asStateFlow() fun onEvent(event: ProfileEvent) { when (event) { is ProfileEvent.Refresh -> loadProfile() is ProfileEvent.UpdateName -> updateName(event.name) } } } // Compose screen @Composable fun ProfileScreen(viewModel: ProfileViewModel = viewModel()) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() ProfileContent( uiState = uiState, onEvent = viewModel::onEvent // Event delegation ) } ``` ### 19. Hoe test je composables? Compose biedt een testbibliotheek met `ComposeTestRule` voor UI-tests en semantische assertions. ```kotlin // ComposableTestExample.kt @get:Rule val composeTestRule = createComposeRule() @Test fun counter_incrementsOnClick() { composeTestRule.setContent { Counter() // The composable under test } // Verify initial state composeTestRule.onNodeWithText("Clicks: 0").assertIsDisplayed() // Simulate a click composeTestRule.onNodeWithText("Clicks: 0").performClick() // Verify new state composeTestRule.onNodeWithText("Clicks: 1").assertIsDisplayed() } ``` Voor het unit-testen van stateless composables is het vaak efficiënter om het ViewModel apart te testen met standaard JUnit/Turbine-tests. ### 20. Hoe integreer je Compose in een bestaande XML-gebaseerde app? De interoperabiliteit is bidirectioneel: `ComposeView` embedt Compose in XML, en `AndroidView` gebruikt klassieke views binnen Compose. ```kotlin // InteropExample.kt // Compose in XML (in a Fragment or Activity) class ProfileFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) setContent { AppTheme { ProfileScreen() } } } } } // XML View in Compose @Composable fun LegacyMapView() { AndroidView( factory = { context -> MapView(context).apply { onCreate(null) } }, update = { mapView -> mapView.getMapAsync { /* config */ } } ) } ``` > **Aanbevolen migratiestrategie** > > Migreer scherm voor scherm, te beginnen met de eenvoudigste schermen. Elk nieuw scherm moet volledig in Compose worden gebouwd, terwijl bestaande schermen geleidelijk worden gemigreerd. ## Conclusie Deze 20 vragen behandelen de basis die elke Android-ontwikkelaar moet beheersen voor een Jetpack Compose interview. Hier een samenvatting als checklist: - ✅ Recomposition en het optimistische gedrag begrijpen - ✅ `remember`, `rememberSaveable` en `derivedStateOf` beheersen - ✅ State hoisting systematisch toepassen - ✅ Side effects kennen (`LaunchedEffect`, `DisposableEffect`, `SideEffect`) - ✅ Performance optimaliseren (stabiele klassen, keys, lambda's) - ✅ Schermen structureren met ViewModel + UiState + Events - ✅ Composables testen met `ComposeTestRule` - ✅ Compose/Views-interoperabiliteit beheersen --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/android/jetpack-compose-interview-questions