# 20 Pertanyaan Wawancara Jetpack Compose Teratas di Tahun 2026 > 20 pertanyaan wawancara Jetpack Compose yang paling sering ditanyakan: recomposition, state management, navigation, performa, dan pola arsitektur dengan contoh kode lengkap. - Published: 2026-01-27 - Updated: 2026-04-12 - Author: SharpSkill - Tags: jetpack compose, android, interview, kotlin, ui - Reading time: 14 min --- Wawancara Android modern menuntut penguasaan mendalam terhadap Jetpack Compose. Toolkit deklaratif ini telah menjadi standar industri, menggantikan pendekatan XML tradisional. Panduan ini mencakup 20 pertanyaan yang paling sering diajukan, mencakup dasar-dasar hingga pola arsitektur lanjutan. > **Tips Persiapan** > > Pewawancara tidak hanya menguji pengetahuan sintaks. Kemampuan menjelaskan mekanisme internal seperti recomposition dan smart recomposition menjadi pembeda utama antara kandidat yang menghafal dan yang benar-benar memahami framework. ## Dasar-Dasar Jetpack Compose ### 1. Apa perbedaan antara Jetpack Compose dan XML tradisional? Jetpack Compose menggunakan pendekatan **deklaratif**: UI dideskripsikan sebagai fungsi dari state. Ketika state berubah, framework secara otomatis menghitung ulang bagian UI yang terpengaruh. XML menggunakan pendekatan **imperatif**: developer harus secara manual memanipulasi view melalui `findViewById` dan setter. ```kotlin // compose_vs_xml.kt // Compose: declarative - UI is a function of state @Composable fun Greeting(name: String) { // UI automatically updates when name changes Text(text = "Hello, $name!") } // XML equivalent requires: // 1. Layout XML file // 2. findViewById in Activity // 3. Manual setText call // textView.text = "Hello, $name!" ``` Keunggulan Compose meliputi: eliminasi boilerplate, preview langsung di IDE, type safety penuh dengan Kotlin, dan composability yang lebih natural dibandingkan inheritance pada View. ### 2. Apa itu recomposition dan bagaimana cara kerjanya? **Recomposition** adalah proses di mana Compose memanggil ulang fungsi composable ketika state yang dibacanya berubah. Framework melakukan **smart recomposition**: hanya composable yang membaca state yang berubah yang akan dipanggil ulang, bukan seluruh tree. ```kotlin // recomposition_example.kt @Composable fun Counter() { // When count changes, only this composable recomposes var count by remember { mutableStateOf(0) } Column { // This Text recomposes when count changes Text(text = "Count: $count") // This Button also recomposes (same scope) Button(onClick = { count++ }) { Text("Increment") } } } // This composable does NOT recompose when count changes @Composable fun StaticHeader() { Text(text = "This never recomposes due to Counter") } ``` Recomposition bersifat **optimistis** dan dapat dibatalkan. Compose dapat memulai recomposition, membatalkannya jika state berubah lagi, dan memulai ulang dengan state terbaru. ### 3. Apa perbedaan antara remember dan rememberSaveable? `remember` menyimpan nilai selama composable berada dalam composition. Nilai hilang saat configuration change (rotasi layar). `rememberSaveable` menyimpan nilai melewati configuration change menggunakan mekanisme `Bundle` Android. ```kotlin // remember_vs_saveable.kt @Composable fun SearchBar() { // Lost on configuration change (rotation) var query by remember { mutableStateOf("") } // Survives configuration change var savedQuery by rememberSaveable { mutableStateOf("") } TextField( value = savedQuery, onValueChange = { savedQuery = it }, placeholder = { Text("Search...") } ) } ``` Gunakan `remember` untuk state sementara yang tidak perlu bertahan (animasi, state hover). Gunakan `rememberSaveable` untuk input pengguna dan state navigasi yang harus bertahan melewati rotasi layar. ### 4. Bagaimana Compose menangani siklus hidup composable? Setiap composable memiliki tiga fase: **masuk composition** (pertama kali dipanggil), **recomposition** (dipanggil ulang saat state berubah), dan **keluar composition** (dihapus dari tree). Lifecycle ini berbeda dari Activity/Fragment lifecycle. ```kotlin // composable_lifecycle.kt @Composable fun UserProfile(userId: String) { // Enters composition: effect starts // Recomposition: effect restarts if userId changes // Leaves composition: effect is cancelled LaunchedEffect(userId) { // Coroutine tied to this composable's lifecycle val user = repository.getUser(userId) } // DisposableEffect for cleanup DisposableEffect(Unit) { val listener = database.addListener { /* ... */ } onDispose { // Called when leaving composition listener.remove() } } } ``` Memahami lifecycle ini krusial untuk mengelola side effect dan menghindari memory leak. ## State Management ### 5. Apa itu state hoisting dan mengapa penting? **State hoisting** adalah pola memindahkan state ke caller composable. Composable yang menerima state menjadi **stateless** dan lebih mudah diuji serta di-reuse. State diangkat ke level terendah yang mencakup semua consumer. ```kotlin // state_hoisting.kt // Stateless: receives state, emits events @Composable fun EmailInput( email: String, onEmailChange: (String) -> Unit, modifier: Modifier = Modifier ) { TextField( value = email, onValueChange = onEmailChange, label = { Text("Email") }, modifier = modifier ) } // Stateful: owns and manages state @Composable fun LoginScreen() { var email by rememberSaveable { mutableStateOf("") } var password by rememberSaveable { mutableStateOf("") } Column { EmailInput( email = email, onEmailChange = { email = it } ) PasswordInput( password = password, onPasswordChange = { password = it } ) } } ``` Pola ini mengikuti prinsip **unidirectional data flow**: state mengalir ke bawah, event mengalir ke atas. ### 6. Kapan menggunakan derivedStateOf? `derivedStateOf` menciptakan state yang dihitung dari state lain, dan hanya memicu recomposition ketika hasil perhitungan berubah. Berguna untuk menghindari recomposition yang tidak perlu. ```kotlin // derived_state.kt @Composable fun ShoppingCart(items: List) { // Without derivedStateOf: recomposes on EVERY list change // val total = items.sumOf { it.price } // With derivedStateOf: recomposes only when total changes val total by remember(items) { derivedStateOf { items.sumOf { it.price } } } // Only recomposes when hasDiscount value changes val hasDiscount by remember(total) { derivedStateOf { total > 100.0 } } Text("Total: $$total") if (hasDiscount) { Text("Discount applied!") } } ``` Gunakan `derivedStateOf` ketika state sumber sering berubah namun hasil turunannya jarang berubah. Jangan gunakan untuk transformasi sederhana yang selalu menghasilkan nilai berbeda. ### 7. Bagaimana perbedaan StateFlow dengan Compose State? `StateFlow` berasal dari Kotlin coroutines dan bersifat platform-agnostic. `MutableState` dari Compose terintegrasi langsung dengan sistem recomposition. Keduanya saling melengkapi dalam arsitektur modern. ```kotlin // stateflow_vs_compose_state.kt // ViewModel with StateFlow class UserViewModel : ViewModel() { private val _uiState = MutableStateFlow(UserUiState()) val uiState: StateFlow = _uiState.asStateFlow() fun updateName(name: String) { _uiState.update { it.copy(name = name) } } } data class UserUiState( val name: String = "", val isLoading: Boolean = false ) // Composable collecting StateFlow @Composable fun UserScreen(viewModel: UserViewModel = viewModel()) { // collectAsState bridges StateFlow to Compose State val uiState by viewModel.uiState.collectAsState() if (uiState.isLoading) { CircularProgressIndicator() } else { Text(uiState.name) } } ``` Gunakan `StateFlow` di ViewModel untuk logika bisnis. Gunakan `MutableState` untuk state UI lokal dalam composable. ## Side Effect ### 8. Apa saja Side Effect API utama di Compose? Compose menyediakan beberapa API untuk menjalankan operasi yang memiliki efek samping di luar scope composable: `LaunchedEffect`, `DisposableEffect`, `SideEffect`, dan `rememberCoroutineScope`. ```kotlin // side_effects.kt @Composable fun AnalyticsScreen(screenName: String) { // LaunchedEffect: runs suspend function, restarts on key change LaunchedEffect(screenName) { analytics.logScreenView(screenName) } // DisposableEffect: setup + cleanup DisposableEffect(Unit) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { analytics.logResume() } } lifecycle.addObserver(observer) onDispose { lifecycle.removeObserver(observer) } } // SideEffect: runs on every successful recomposition SideEffect { // Update non-Compose state analyticsHelper.setCurrentScreen(screenName) } } @Composable fun ActionButton() { // rememberCoroutineScope: for event-driven coroutines val scope = rememberCoroutineScope() Button(onClick = { scope.launch { // Cannot use LaunchedEffect here (not composable context) performAction() } }) { Text("Execute") } } ``` Aturannya: `LaunchedEffect` untuk operasi yang terkait lifecycle composable, `rememberCoroutineScope` untuk operasi yang dipicu event pengguna. ### 9. Bagaimana cara mengelola coroutine di dalam Compose? Coroutine dalam Compose dikelola melalui scope yang terikat pada lifecycle composable. `LaunchedEffect` membatalkan coroutine ketika composable keluar dari composition atau key berubah. ```kotlin // coroutines_in_compose.kt @Composable fun SearchScreen(query: String) { var results by remember { mutableStateOf>(emptyList()) } // Debounced search: cancels previous on new query LaunchedEffect(query) { delay(300) // debounce results = searchRepository.search(query) } } @Composable fun InfiniteAnimation() { val infiniteTransition = rememberInfiniteTransition(label = "pulse") val alpha by infiniteTransition.animateFloat( initialValue = 0.3f, targetValue = 1f, animationSpec = infiniteRepeatable( animation = tween(1000), repeatMode = RepeatMode.Reverse ), label = "alpha" ) Box( modifier = Modifier .size(100.dp) .alpha(alpha) .background(Color.Blue) ) } ``` Jangan pernah meluncurkan coroutine di dalam body composable tanpa side effect API. Hal ini menyebabkan coroutine baru diluncurkan setiap recomposition. ## Layout ### 10. Bagaimana cara kerja LazyColumn dan optimasinya? `LazyColumn` hanya meng-compose dan me-layout item yang terlihat di layar, analog dengan `RecyclerView`. Parameter `key` krusial untuk performa karena membantu Compose mengidentifikasi item secara unik. ```kotlin // lazy_column.kt @Composable fun UserList(users: List) { LazyColumn( contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { items( items = users, // Key helps Compose track items across changes key = { user -> user.id } ) { user -> UserCard(user = user) } } } // Optimized with remember and stable types @Composable fun UserCard(user: User) { // Skipped during recomposition if user hasn't changed Card { Column(modifier = Modifier.padding(16.dp)) { Text(text = user.name, style = MaterialTheme.typography.titleMedium) Text(text = user.email, style = MaterialTheme.typography.bodyMedium) } } } ``` Hindari operasi berat di dalam lambda `items`. Gunakan `remember` untuk komputasi yang mahal dan pastikan data class digunakan agar Compose dapat melakukan equality check dengan benar. ### 11. Bagaimana cara membuat custom Layout di Compose? Custom `Layout` memungkinkan kontrol penuh terhadap pengukuran dan penempatan child. Proses ini melibatkan dua fase: **measure** (mengukur setiap child) dan **place** (menempatkan child di posisi yang tepat). ```kotlin // custom_layout.kt @Composable fun StaggeredGrid( modifier: Modifier = Modifier, columns: Int = 2, content: @Composable () -> Unit ) { Layout( content = content, modifier = modifier ) { measurables, constraints -> val columnWidth = constraints.maxWidth / columns val itemConstraints = constraints.copy( minWidth = columnWidth, maxWidth = columnWidth ) // Measure phase val placeables = measurables.map { it.measure(itemConstraints) } // Track height per column val columnHeights = IntArray(columns) val positions = placeables.map { placeable -> val col = columnHeights.indexOfMin() val position = IntOffset(col * columnWidth, columnHeights[col]) columnHeights[col] += placeable.height position } val height = columnHeights.max() // Place phase layout(constraints.maxWidth, height) { placeables.forEachIndexed { index, placeable -> placeable.placeRelative(positions[index]) } } } } ``` Custom Layout sebaiknya digunakan ketika Row, Column, dan Box tidak cukup memenuhi kebutuhan. Untuk kasus yang lebih sederhana, `Modifier.layout` sudah memadai. ### 12. Bagaimana cara menggunakan MaterialTheme di Compose? `MaterialTheme` menyediakan sistem desain yang konsisten melalui tiga pilar: **color scheme**, **typography**, dan **shapes**. Material 3 (Material You) mendukung dynamic color berdasarkan wallpaper pengguna. ```kotlin // material_theme.kt // Custom theme definition @Composable fun AppTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit ) { val colorScheme = if (darkTheme) { darkColorScheme( primary = Color(0xFFBB86FC), secondary = Color(0xFF03DAC6), background = Color(0xFF121212) ) } else { lightColorScheme( primary = Color(0xFF6200EE), secondary = Color(0xFF03DAC6), background = Color(0xFFFFFFFF) ) } MaterialTheme( colorScheme = colorScheme, typography = AppTypography, shapes = AppShapes, content = content ) } // Usage in composables @Composable fun ThemedButton() { Button( onClick = { }, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primary ) ) { Text( text = "Themed", style = MaterialTheme.typography.labelLarge ) } } ``` Selalu gunakan token dari `MaterialTheme` daripada hardcode warna atau ukuran font. Ini memastikan konsistensi dan memudahkan implementasi dark mode. ## Navigasi ### 13. Bagaimana cara mengimplementasikan navigasi dengan NavHost? `NavHost` dari library Navigation Compose mengelola back stack dan transisi antar layar. Setiap destinasi didefinisikan sebagai composable dengan route unik. ```kotlin // navigation.kt @Composable fun AppNavigation() { val navController = rememberNavController() NavHost( navController = navController, startDestination = "home" ) { composable("home") { HomeScreen( onNavigateToDetail = { id -> navController.navigate("detail/$id") } ) } composable( route = "detail/{itemId}", arguments = listOf( navArgument("itemId") { type = NavType.StringType } ) ) { backStackEntry -> val itemId = backStackEntry.arguments?.getString("itemId") DetailScreen(itemId = itemId) } composable("settings") { SettingsScreen( onBack = { navController.popBackStack() } ) } } } ``` Jangan pernah meneruskan `navController` langsung ke composable anak. Gunakan callback lambda agar composable tetap testable dan tidak bergantung pada navigasi. ### 14. Bagaimana cara mengirim data antar layar di Compose? Data sederhana dikirim melalui argumen route. Untuk objek kompleks, gunakan `savedStateHandle` dari ViewModel atau shared ViewModel dengan scope navigation graph. ```kotlin // passing_data.kt // Simple arguments via route navController.navigate("detail/${item.id}") // Complex data via savedStateHandle class DetailViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { val itemId: String = checkNotNull(savedStateHandle["itemId"]) // Or pass result back to previous screen fun setResult(result: String) { savedStateHandle["result"] = result } } // Reading result in previous screen @Composable fun ListScreen(navController: NavController) { val result = navController.currentBackStackEntry ?.savedStateHandle ?.getStateFlow("result", "") ?.collectAsState() // Use result } // Type-safe navigation (recommended) @Serializable data class DetailRoute(val itemId: String) // In NavHost composable { backStackEntry -> val route = backStackEntry.toRoute() DetailScreen(itemId = route.itemId) } ``` Hindari mengirim objek besar melalui argumen navigasi. Kirim hanya identifier, lalu muat data lengkap di ViewModel layar tujuan. ## Performa ### 15. Bagaimana cara mencegah recomposition yang tidak perlu? Recomposition yang berlebihan menurunkan performa. Strategi utama: gunakan **stable types**, **lambda stabilization**, dan **Compose compiler reports** untuk mengidentifikasi masalah. ```kotlin // preventing_recomposition.kt // Stable data class - Compose can skip recomposition @Immutable data class UserData( val id: String, val name: String, val avatarUrl: String ) // Lambda stabilization with remember @Composable fun ParentScreen(viewModel: MyViewModel = viewModel()) { // Without remember: new lambda instance on every recomposition // UserList(onItemClick = { id -> viewModel.selectItem(id) }) // With remember: stable lambda reference val onItemClick = remember<(String) -> Unit> { { id -> viewModel.selectItem(id) } } UserList(onItemClick = onItemClick) } // Using key to control recomposition scope @Composable fun ItemList(items: List) { LazyColumn { items(items, key = { it.id }) { item -> // Each item recomposes independently key(item.id) { ItemRow(item = item) } } } } ``` Aktifkan Compose compiler metrics (`-P plugin:...metricsDestination`) untuk mengidentifikasi composable yang tidak stabil. ### 16. Bagaimana cara melakukan profiling aplikasi Compose? Android Studio menyediakan **Layout Inspector** yang menampilkan recomposition count secara real-time. Compose juga menyediakan modifier khusus untuk debugging. ```kotlin // profiling.kt // Recomposition counter (debug only) @Composable fun DebugRecomposition(tag: String) { if (BuildConfig.DEBUG) { val recompositionCount = remember { mutableIntStateOf(0) } SideEffect { recompositionCount.intValue++ } Log.d("Recomposition", "$tag: ${recompositionCount.intValue}") } } // Performance tracing @Composable fun TracedScreen() { trace("TracedScreen") { // Content here is measured in system trace HeavyContent() } } // Baseline profiles for startup optimization @ExperimentalBaselineProfilesApi class BaselineProfileGenerator { @get:Rule val rule = BaselineProfileRule() @Test fun generateProfile() { rule.collect("com.example.app") { startActivityAndWait() // Navigate through critical user flows } } } ``` Gunakan Layout Inspector di Android Studio untuk memonitor recomposition count. Composable dengan count tinggi tanpa perubahan visual mengindikasikan masalah performa. ### 17. Mengapa urutan Modifier penting? Modifier di Compose diterapkan secara **berurutan dari luar ke dalam**. Urutan yang berbeda menghasilkan hasil visual yang berbeda. Kesalahan urutan Modifier adalah sumber bug UI yang umum. ```kotlin // modifier_order.kt @Composable fun ModifierOrderDemo() { // Order 1: padding THEN background // Background covers padded area Box( modifier = Modifier .padding(16.dp) .background(Color.Red) .size(100.dp) ) // Order 2: background THEN padding // Background extends to full size, padding is inside Box( modifier = Modifier .background(Color.Blue) .padding(16.dp) .size(100.dp) ) // Clickable area depends on order Box( modifier = Modifier .padding(16.dp) // Padding outside clickable .clickable { } // Click area .padding(8.dp) // Padding inside clickable .background(Color.Green) ) } ``` Aturan praktis: baca chain Modifier dari atas ke bawah sebagai lapisan luar ke dalam. `padding` sebelum `background` menambahkan ruang di luar background, sedangkan setelahnya menambahkan ruang di dalam. > **Kesalahan Umum** > > Menempatkan `clickable` setelah `padding` membuat area padding tidak dapat diklik. Untuk area sentuh yang lebih besar, tempatkan `clickable` sebelum `padding`. ## Arsitektur ### 18. Bagaimana pola ViewModel + UiState bekerja dengan Compose? Pola ini memisahkan logika bisnis (ViewModel) dari presentasi (Composable). ViewModel mengekspos satu `StateFlow` yang merepresentasikan keseluruhan state layar. ```kotlin // viewmodel_uistate.kt // Sealed interface for complete state representation sealed interface HomeUiState { data object Loading : HomeUiState data class Success( val articles: List
, val isRefreshing: Boolean = false ) : HomeUiState data class Error(val message: String) : HomeUiState } class HomeViewModel( private val repository: ArticleRepository ) : ViewModel() { val uiState: StateFlow = repository .getArticles() .map, HomeUiState> { HomeUiState.Success(it) } .catch { emit(HomeUiState.Error(it.message ?: "Unknown error")) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = HomeUiState.Loading ) fun refresh() { viewModelScope.launch { // Update state to show refreshing repository.refresh() } } } // Composable consumes UiState @Composable fun HomeScreen(viewModel: HomeViewModel = viewModel()) { val uiState by viewModel.uiState.collectAsState() when (val state = uiState) { is HomeUiState.Loading -> LoadingIndicator() is HomeUiState.Success -> ArticleList( articles = state.articles, onRefresh = viewModel::refresh ) is HomeUiState.Error -> ErrorMessage(state.message) } } ``` Gunakan `sealed interface` untuk UiState agar Compose dapat melakukan exhaustive `when` check. `WhileSubscribed(5000)` menghentikan collection saat tidak ada subscriber, menghemat resource. ### 19. Bagaimana cara menguji composable? Compose menyediakan `ComposeTestRule` untuk pengujian UI. Test ditulis menggunakan semantic tree, bukan implementasi visual. ```kotlin // compose_testing.kt class LoginScreenTest { @get:Rule val composeTestRule = createComposeRule() @Test fun loginButton_disabledWhenFieldsEmpty() { composeTestRule.setContent { LoginScreen() } // Find by test tag composeTestRule .onNodeWithTag("loginButton") .assertIsNotEnabled() } @Test fun loginButton_enabledWhenFieldsFilled() { composeTestRule.setContent { LoginScreen() } // Type in fields composeTestRule .onNodeWithTag("emailField") .performTextInput("test@example.com") composeTestRule .onNodeWithTag("passwordField") .performTextInput("password123") // Verify button is enabled composeTestRule .onNodeWithTag("loginButton") .assertIsEnabled() } @Test fun errorMessage_displayedOnFailure() { composeTestRule.setContent { LoginScreen() } // Verify error message appears composeTestRule .onNodeWithText("Invalid credentials") .assertExists() } } ``` Gunakan `testTag` di Modifier untuk memudahkan pencarian node. Hindari pengujian berdasarkan posisi visual karena rapuh terhadap perubahan layout. ### 20. Bagaimana cara mengintegrasikan Compose dengan View XML yang sudah ada? Migrasi bertahap dari XML ke Compose dimungkinkan melalui `ComposeView` (Compose di dalam XML) dan `AndroidView` (XML di dalam Compose). Strategi ini memungkinkan adopsi Compose tanpa menulis ulang seluruh aplikasi. ```kotlin // interop.kt // Compose inside XML layout class ExistingFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) setContent { AppTheme { NewComposeFeature() } } } } } // XML View inside Compose @Composable fun LegacyMapView() { AndroidView( factory = { context -> // Create traditional Android View MapView(context).apply { // Configure the view onCreate(Bundle()) } }, update = { mapView -> // Update view when state changes mapView.getMapAsync { map -> map.moveCamera(CameraUpdateFactory.newLatLng(location)) } }, onRelease = { mapView -> // Cleanup when leaving composition mapView.onDestroy() } ) } ``` Strategi migrasi yang direkomendasikan: mulai dengan layar baru menggunakan Compose, lalu migrasikan layar yang sudah ada secara bertahap dari leaf composable ke atas. ## Kesimpulan Ke-20 pertanyaan ini mencakup aspek-aspek krusial wawancara Jetpack Compose: dari dasar-dasar deklaratif hingga pola arsitektur production-ready. Kunci keberhasilan terletak pada pemahaman mendalam terhadap mekanisme recomposition, pengelolaan state yang tepat, dan pengalaman praktis membangun aplikasi nyata. ### Checklist Persiapan - Kuasai perbedaan Compose deklaratif vs XML imperatif - Pahami mekanisme recomposition dan smart recomposition - Latih state hoisting dan unidirectional data flow - Implementasikan side effect dengan benar (LaunchedEffect, DisposableEffect) - Optimalkan LazyColumn dengan key dan stable types - Kuasai pola ViewModel + UiState dengan sealed interface - Tulis compose test menggunakan semantic tree - Pahami interop Compose-XML untuk migrasi bertahap Praktek langsung dengan proyek nyata tetap menjadi metode paling efektif untuk menguasai konsep-konsep ini. Setiap pertanyaan yang dibahas dalam panduan ini layak dieksplorasi lebih dalam dengan implementasi kode yang sesungguhnya. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/android/jetpack-compose-interview-questions