Die 20 wichtigsten Jetpack Compose Interviewfragen 2026

Die 20 am häufigsten gestellten Jetpack Compose Interviewfragen: Recomposition, State-Management, Navigation, Performance und Architekturmuster.

Jetpack Compose Interviewfragen für Android-Entwickler

Jetpack Compose hat sich als Standard-UI-Toolkit für die Android-Entwicklung etabliert. Technische Interviews prüfen mittlerweile routinemäßig Compose-Kenntnisse ab — von Recomposition-Mechanismen über State-Management bis hin zu Performance-Optimierung. Hier sind die 20 am häufigsten gestellten Fragen mit ausführlichen Antworten und Codebeispielen.

So nutzt man diesen Leitfaden

Jede Frage enthält eine strukturierte Antwort und ein Codebeispiel. Die Fragen sind nach aufsteigendem Schwierigkeitsgrad geordnet: Grundlagen, Mittelstufe, dann Fortgeschrittene.

Jetpack Compose Grundlagen

1. Was ist der Unterschied zwischen Compose und dem XML-View-System?

Compose verwendet ein deklaratives Paradigma: Die UI wird als Funktion des Zustands beschrieben, und das Framework übernimmt Updates automatisch. Das traditionelle XML-System ist imperativ — Views müssen manuell über findViewById oder View Binding manipuliert werden.

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
    }
}

Mit Compose entfällt die Notwendigkeit, eine TextView-Referenz zu suchen und manuell zu aktualisieren — Recomposition erledigt alles.

2. Was ist Recomposition?

Recomposition ist der Prozess, bei dem Compose @Composable-Funktionen erneut aufruft, wenn sich deren Zustand ändert. Nur Funktionen, deren Parameter sich geändert haben, werden erneut ausgeführt, was die Performance optimiert.

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
}

Wichtiger Punkt für Interviews: Recomposition ist optimistisch (Compose geht davon aus, dass sie abgebrochen werden kann) und ungeordnet (die Ausführungsreihenfolge der Composables ist nicht garantiert).

3. Was bewirkt remember?

remember bewahrt einen Wert über Recompositions hinweg. Ohne remember würde jede Recomposition die Variable auf ihren Initialwert zurücksetzen.

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. Was ist der Unterschied zwischen remember und rememberSaveable?

remember bewahrt Werte über Recompositions hinweg, verliert sie jedoch bei Konfigurationsänderungen (Bildschirmrotation). rememberSaveable persistiert Werte auch über Konfigurationsänderungen hinweg mithilfe des SavedInstanceState-Mechanismus.

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. Was ist State Hoisting?

State Hoisting bedeutet, den Zustand von einem Composable in seine übergeordnete Komponente zu verlagern. Das Kind-Composable wird zustandslos: Es empfängt den Zustand als Parameter und meldet Änderungen über 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
    )
}

Dieses Muster ist fundamental in Compose und taucht in Interviews regelmäßig auf.

6. Wie funktioniert derivedStateOf?

derivedStateOf erstellt einen abgeleiteten Zustand, der Recomposition nur dann auslöst, wenn sich das Berechnungsergebnis ändert — nicht bei jeder Änderung der Quelle.

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) }
        }
    }
}
Wann derivedStateOf verwenden

Dieser Mechanismus ist nützlich, wenn sich ein Zustand häufig ändert, aber das abgeleitete Ergebnis sich selten ändert (z.B. eine gefilterte Liste, ein Button der basierend auf der Formularvalidierung aktiviert/deaktiviert wird).

7. Was ist der Unterschied zwischen StateFlow und Compose State<T>?

StateFlow (Kotlin Coroutines) ist ein reaktiver Stream aus dem ViewModel. State<T> ist der native Mechanismus von Compose zum Auslösen von Recomposition. In der Praxis wird StateFlow in einem Composable über collectAsStateWithLifecycle() gesammelt.

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}")
}

Die Empfehlung lautet collectAsStateWithLifecycle() zu verwenden (statt collectAsState()), da es den Lifecycle respektiert und die Sammlung stoppt, wenn der Bildschirm nicht mehr sichtbar ist.

Side Effects und Lifecycle

8. Was sind die wichtigsten Side Effects in Compose?

Side Effects ermöglichen die kontrollierte Ausführung von nicht-composablem Code (Netzwerkaufrufe, Logging, Navigation). Die drei wichtigsten:

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. Wann LaunchedEffect vs rememberCoroutineScope verwenden?

LaunchedEffect ist an die Composition gebunden: Die Coroutine wird abgebrochen, wenn das Composable die Composition verlässt oder sich der Key ändert. rememberCoroutineScope liefert einen vom Benutzer kontrollierten Scope, nützlich für benutzerausgelöste Aktionen (Button-Klicks).

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")
    }
}

Bereit für deine Android-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Layouts und erweiterte Komponenten

10. Wie funktioniert LazyColumn und wie unterscheidet es sich von RecyclerView?

LazyColumn ist das Compose-Äquivalent von RecyclerView. Es komponiert nur die auf dem Bildschirm sichtbaren Elemente und recycelt Composables, die aus dem sichtbaren Bereich 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)
        }
    }
}

Wichtiger Punkt: Immer einen stabilen key-Parameter angeben, um unnötige Recompositions beim Sortieren oder Entfernen von Elementen zu vermeiden.

11. Wie erstellt man ein benutzerdefiniertes Layout?

Compose erlaubt die Erstellung benutzerdefinierter Layouts über die Layout-Funktion. Dies ersetzt benutzerdefinierte ViewGroup-Implementierungen aus dem View-System.

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. Wie implementiert man benutzerdefiniertes Theming mit MaterialTheme?

Theming in Compose basiert auf CompositionLocal. MaterialTheme stellt Farb-, Typografie- und Formwerte bereit, die im gesamten Composable-Baum zugänglich sind.

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. Wie funktioniert Compose Navigation?

Compose Navigation verwendet einen NavHost mit Routen, die als Strings deklariert werden (oder als serialisierbare Typen seit 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. Wie übergibt man Daten zwischen Screens?

Einfache Argumente (String, Int) werden direkt über die Route übergeben. Für komplexe Objekte empfiehlt sich die Verwendung eines gemeinsamen ViewModel oder die Übergabe einer ID mit anschließendem Laden der Daten im Ziel-Screen.

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-Pattern vermeiden

Komplexe serialisierte Objekte niemals in der Route übergeben. Stattdessen eine ID übergeben und den Ziel-Screen die Daten über das ViewModel laden lassen.

Performance und Optimierung

15. Wie verhindert man unnötige Recompositions?

Drei Hauptstrategien zur Minimierung unnötiger Recompositions:

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. Wie profiliert man die Performance einer Compose-App?

Der Layout Inspector in Android Studio zeigt Recomposition-Zähler pro Composable an. Das debugInspectorInfo-Flag und der CompositionTracer helfen bei der Diagnose.

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)
}

Zusätzlich generieren die Compose Compiler Metrics einen detaillierten Bericht über überspringbare, neustartbare Funktionen und stabile/instabile Klassen.

17. Was ist der Modifier und warum ist er wichtig?

Modifier ist eine geordnete Kette von Anweisungen, die das Erscheinungsbild und Verhalten eines Composable modifiziert. Die Reihenfolge der Modifier beeinflusst direkt das 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: Immer einen modifier: Modifier = Modifier-Parameter in wiederverwendbaren Composables akzeptieren, um Anpassungen durch die übergeordnete Komponente zu ermöglichen.

Architektur und fortgeschrittene Muster

18. Wie strukturiert man einen Compose-Screen mit einem ViewModel?

Das empfohlene Muster trennt den UI-Zustand in einer data class, Events in einem sealed interface, und das ViewModel übernimmt die Geschäftslogik.

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. Wie testet man Composables?

Compose bietet eine Testbibliothek mit ComposeTestRule für UI-Tests und 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()
}

Beim Unit-Testing zustandsloser Composables ist es oft effizienter, das ViewModel separat mit Standard-JUnit/Turbine-Tests zu testen.

20. Wie integriert man Compose in eine bestehende XML-basierte App?

Die Interoperabilität ist bidirektional: ComposeView bettet Compose in XML ein, und AndroidView nutzt klassische Views innerhalb von 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 */ } }
    )
}
Empfohlene Migrationsstrategie

Bildschirm für Bildschirm migrieren, beginnend mit den einfachsten Screens. Jeder neue Screen sollte vollständig in Compose erstellt werden, während bestehende Screens schrittweise migriert werden.

Bereit für deine Android-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Fazit

Diese 20 Fragen decken die Grundlagen ab, die jeder Android-Entwickler für ein Jetpack Compose Interview beherrschen muss. Hier eine Zusammenfassung als Checkliste:

  • ✅ Recomposition und ihr optimistisches Verhalten verstehen
  • remember, rememberSaveable und derivedStateOf beherrschen
  • ✅ State Hoisting systematisch anwenden
  • ✅ Side Effects kennen (LaunchedEffect, DisposableEffect, SideEffect)
  • ✅ Performance optimieren (stabile Klassen, Keys, Lambdas)
  • ✅ Screens mit ViewModel + UiState + Events strukturieren
  • ✅ Composables mit ComposeTestRule testen
  • ✅ Compose/Views-Interoperabilität beherrschen

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Tags

#jetpack compose
#android
#interview
#kotlin
#ui

Teilen

Verwandte Artikel