De 20 meest gestelde Jetpack Compose interviewvragen in 2026

De 20 meest gestelde Jetpack Compose interviewvragen: recomposition, state management, navigatie, performance en architectuurpatronen.

Jetpack Compose interviewvragen voor Android-ontwikkelaars

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.

DeclarativeExample.ktkotlin
// 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.

RecompositionExample.ktkotlin
@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.

RememberExample.ktkotlin
@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.

RememberSaveableExample.ktkotlin
@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.

StateHoistingExample.ktkotlin
// ✅ 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.

DerivedStateExample.ktkotlin
@Composable
fun FilteredList(items: List<String>) {
    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<T>?

StateFlow (Kotlin Coroutines) is een reactieve stream vanuit het ViewModel. State<T> is het native mechanisme van Compose om recomposition te triggeren. In de praktijk wordt StateFlow in een composable verzameld via collectAsStateWithLifecycle().

StateFlowExample.ktkotlin
class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UserUiState())
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
}

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    // Converts StateFlow to State<T> 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:

SideEffectsExample.ktkotlin
@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).

CoroutineScopeExample.ktkotlin
@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")
    }
}

Klaar om je Android gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

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.

LazyColumnExample.ktkotlin
@Composable
fun UserList(users: List<User>) {
    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.

CustomLayoutExample.ktkotlin
@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.

CustomThemeExample.ktkotlin
// 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
        )
    }
}

13. Hoe werkt Compose Navigation?

Compose Navigation gebruikt een NavHost met routes gedeclareerd als strings (of serialiseerbare typen sinds Navigation 2.8+).

NavigationExample.ktkotlin
@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.

NavigationArgsExample.ktkotlin
// Type-safe navigation with Kotlin Serialization (Navigation 2.8+)
@Serializable
data class ProfileRoute(val userId: String, val tab: String = "info")

// Declaration
composable<ProfileRoute> { backStackEntry ->
    val route = backStackEntry.toRoute<ProfileRoute>()
    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:

PerformanceExample.ktkotlin
// 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<User>) {
    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.

ProfilingExample.ktkotlin
// 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.

ModifierOrderExample.ktkotlin
@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.

ScreenArchitectureExample.ktkotlin
// 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.

ComposableTestExample.ktkotlin
@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.

InteropExample.ktkotlin
// 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.

Klaar om je Android gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

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

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Tags

#jetpack compose
#android
#interview
#kotlin
#ui

Delen

Gerelateerde artikelen