Las 20 preguntas más frecuentes de Jetpack Compose en entrevistas (2026)
Las 20 preguntas de entrevista sobre Jetpack Compose más comunes: recomposición, gestión de estado, navegación, rendimiento y patrones de arquitectura.

Jetpack Compose se ha consolidado como el toolkit de UI estándar para el desarrollo Android. Las entrevistas técnicas evalúan con regularidad el dominio de Compose, desde los mecanismos de recomposición hasta la gestión de estado y la optimización de rendimiento. A continuación se presentan las 20 preguntas más frecuentes, con respuestas detalladas y ejemplos de código.
Cada pregunta incluye una respuesta estructurada y un ejemplo de código. Las preguntas están organizadas por dificultad creciente: fundamentos, intermedio y avanzado.
Fundamentos de Jetpack Compose
1. ¿Cuál es la diferencia entre Compose y el sistema de vistas XML?
Compose utiliza un paradigma declarativo: la UI se describe como una función del estado, y el framework se encarga de las actualizaciones automáticamente. El sistema XML tradicional es imperativo — las vistas deben manipularse manualmente mediante findViewById o View Binding.
// 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
}
}Con Compose, no es necesario buscar una referencia a un TextView y actualizarlo manualmente — la recomposición se encarga de todo.
2. ¿Qué es la recomposición?
La recomposición es el proceso mediante el cual Compose vuelve a invocar las funciones @Composable cuando su estado cambia. Solo se re-ejecutan las funciones cuyos parámetros han cambiado, lo que optimiza el rendimiento.
@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
}Punto clave para entrevistas: la recomposición es optimista (Compose asume que puede cancelarse) y no ordenada (el orden de ejecución de los composables no está garantizado).
3. ¿Qué hace remember?
remember preserva un valor a través de las recomposiciones. Sin remember, cada recomposición restablecería la variable a su valor inicial.
@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. ¿Cuál es la diferencia entre remember y rememberSaveable?
remember preserva valores entre recomposiciones pero los pierde en cambios de configuración (rotación de pantalla). rememberSaveable persiste los valores a través de cambios de configuración usando el mecanismo SavedInstanceState.
@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...") }
)
}Gestión de estado en Compose
5. ¿Qué es el state hoisting?
El state hoisting consiste en mover el estado de un composable hacia su componente padre. El composable hijo se vuelve sin estado: recibe el estado como parámetros y notifica los cambios mediante callbacks.
// ✅ 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
)
}Este patrón es fundamental en Compose y aparece con frecuencia en entrevistas.
6. ¿Cómo funciona derivedStateOf?
derivedStateOf crea un estado derivado que solo dispara la recomposición cuando el resultado del cálculo cambia, no en cada modificación de la fuente.
@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) }
}
}
}Este mecanismo resulta útil cuando un estado cambia frecuentemente pero el resultado derivado cambia rara vez (por ejemplo, una lista filtrada o un botón habilitado/deshabilitado según la validez de un formulario).
7. ¿Cuál es la diferencia entre StateFlow y State<T> de Compose?
StateFlow (Kotlin coroutines) es un flujo reactivo proveniente del ViewModel. State<T> es el mecanismo nativo de Compose para disparar recomposiciones. En la práctica, StateFlow se recolecta en un composable mediante collectAsStateWithLifecycle().
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}")
}La recomendación es usar collectAsStateWithLifecycle() (en lugar de collectAsState()) porque respeta el ciclo de vida y detiene la recolección cuando la pantalla deja de ser visible.
Side effects y ciclo de vida
8. ¿Cuáles son los principales side effects en Compose?
Los side effects permiten ejecutar código no-composable (llamadas de red, logging, navegación) de manera controlada. Los tres principales:
@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. ¿Cuándo usar LaunchedEffect vs rememberCoroutineScope?
LaunchedEffect está vinculado a la composición: la coroutine se cancela cuando el composable sale de la composición o cuando la clave cambia. rememberCoroutineScope proporciona un scope controlado por el usuario, útil para acciones disparadas por interacción (clics de botón).
@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")
}
}¿Listo para aprobar tus entrevistas de Android?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Layouts y componentes avanzados
10. ¿Cómo funciona LazyColumn y en qué se diferencia de RecyclerView?
LazyColumn es el equivalente en Compose de RecyclerView. Solo compone los elementos visibles en pantalla y recicla los composables que salen de la ventana visible.
@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)
}
}
}Punto importante: siempre proporcionar un parámetro key estable para evitar recomposiciones innecesarias al ordenar o eliminar elementos.
11. ¿Cómo crear un layout personalizado?
Compose permite crear layouts personalizados mediante la función Layout. Esto reemplaza las implementaciones de ViewGroup personalizadas del sistema de vistas.
@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. ¿Cómo implementar temas personalizados con MaterialTheme?
El sistema de temas en Compose se basa en CompositionLocal. MaterialTheme provee valores de colores, tipografía y formas accesibles en todo el árbol de composables.
// 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
)
}
}Navegación en Compose
13. ¿Cómo funciona Compose Navigation?
Compose Navigation utiliza un NavHost con rutas declaradas como strings (o tipos serializables desde Navigation 2.8+).
@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. ¿Cómo pasar datos entre pantallas?
Los argumentos simples (String, Int) se pasan directamente a través de la ruta. Para objetos complejos, la recomendación actual es usar un ViewModel compartido o pasar solo un identificador y cargar los datos en la pantalla de destino.
// 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"))Nunca pasar objetos complejos serializados en la ruta. Se debe pasar un ID y dejar que la pantalla de destino cargue los datos a través del ViewModel.
Rendimiento y optimización
15. ¿Cómo prevenir recomposiciones innecesarias?
Tres estrategias principales para minimizar las recomposiciones innecesarias:
// 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. ¿Cómo perfilar el rendimiento de una aplicación Compose?
El Layout Inspector de Android Studio muestra los contadores de recomposición por composable. El flag debugInspectorInfo y el CompositionTracer ayudan en el diagnóstico.
// 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)
}Además, Compose Compiler Metrics genera un reporte detallado de funciones skippable, restartable y clases estables/inestables.
17. ¿Qué es el Modifier y por qué es importante?
Modifier es una cadena ordenada de instrucciones que modifica la apariencia y el comportamiento de un composable. El orden de los modifiers impacta directamente en el renderizado.
@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)
)
}Buena práctica: siempre aceptar un parámetro modifier: Modifier = Modifier en composables reutilizables para permitir la personalización por parte del componente padre.
Arquitectura y patrones avanzados
18. ¿Cómo estructurar una pantalla Compose con un ViewModel?
El patrón recomendado separa el estado de la UI en un data class, los eventos en una sealed interface, y el ViewModel maneja la lógica de negocio.
// 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. ¿Cómo testear composables?
Compose proporciona una librería de testing con ComposeTestRule para pruebas de UI y aserciones semánticas.
@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()
}Para pruebas unitarias de composables sin estado, testear el ViewModel por separado con pruebas estándar de JUnit/Turbine suele ser más eficiente.
20. ¿Cómo integrar Compose en una aplicación existente basada en XML?
La interoperabilidad es bidireccional: ComposeView permite insertar Compose en XML, y AndroidView utiliza vistas clásicas dentro de Compose.
// 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 */ } }
)
}Migrar pantalla por pantalla, comenzando por las más simples. Cada nueva pantalla debe ser completamente en Compose, mientras que las pantallas existentes se migran progresivamente.
¿Listo para aprobar tus entrevistas de Android?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Conclusión
Estas 20 preguntas cubren los fundamentos que todo desarrollador Android necesita dominar para una entrevista de Jetpack Compose. A continuación, un checklist de repaso:
- ✅ Comprender la recomposición y su comportamiento optimista
- ✅ Dominar
remember,rememberSaveableyderivedStateOf - ✅ Aplicar el state hoisting sistemáticamente
- ✅ Conocer los side effects (
LaunchedEffect,DisposableEffect,SideEffect) - ✅ Optimizar el rendimiento (clases estables, keys, lambdas)
- ✅ Estructurar pantallas con ViewModel + UiState + Events
- ✅ Testear composables con
ComposeTestRule - ✅ Manejar la interoperabilidad Compose/Views
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
Etiquetas
Compartir
Artículos relacionados

MVVM vs MVI en Android: ¿Qué arquitectura elegir en 2026?
Comparación profunda entre MVVM y MVI en Android: ventajas, limitaciones, casos de uso y una guía práctica para elegir la arquitectura correcta en 2026.

Dominar las Coroutines de Kotlin: Guía Completa 2026
Aprende a dominar las coroutines de Kotlin para desarrollo Android: funciones suspend, scopes, dispatchers y patrones avanzados.

SQL para Analistas de Datos: Window Functions, CTEs y Consultas Avanzadas
Guia completa de SQL window functions (ROW_NUMBER, RANK, LAG/LEAD), Common Table Expressions y patrones avanzados de consultas esenciales para entrevistas de data analyst y trabajo diario con datos.