Las entrevistas de Android se enfocan en idioms de Kotlin, coroutines, y el ciclo de vida de la plataforma — la mayoría de las preguntas senior en realidad preguntan si entiendes que tu proceso puede morir en cualquier momento. Aquí están las preguntas hechas más a menudo, cada una con una respuesta modelo.
Cada pregunta viene con una respuesta modelo con la que puedes comparar la tuya.
1
¿Cómo funciona la null safety de Kotlin, y cuándo es peligroso un platform type?
Respuesta
Kotlin divide cada tipo en nullable (String?) y non-nullable (String), y el compilador se rehúsa a desreferenciar un valor nullable sin una verificación, así que NullPointerException mayormente desaparece. Los platform types surgen en el límite con Java: Kotlin no sabe si un método Java puede devolver null, así que confía en ti. Ahí es de donde todavía vienen los NPE — anota las APIs de Java o trata sus resultados como nullable en el call site.
2
¿Qué te dan las data classes y las sealed classes?
Respuesta
Una data class genera equals, hashCode, toString, copy y destructuring desde el constructor primario, lo que la hace la herramienta correcta para valores que comparas y copias — modelos de API, estado de UI. Una sealed class restringe sus subclases a un conjunto conocido, así un when sobre ella es exhaustivo y el compilador marca cualquier rama que olvides. Juntas modelan el estado de UI limpiamente: una sealed interface para Loading, Content, Error, con data classes llevando el payload.
3
¿Qué es la concurrencia estructurada en coroutines, y qué hace un scope?
Respuesta
Concurrencia estructurada significa que cada coroutine pertenece a un scope y un job padre, así cancelar el padre cancela a todos los hijos y el padre no completa hasta que ellos lo hagan. Eso es lo que previene el trabajo en background con fugas que obtienes de hilos crudos. En Android, viewModelScope se cancela cuando el ViewModel se limpia y lifecycleScope cuando el lifecycle termina, así el trabajo para cuando su resultado ya no se puede usar.
4
Explica los Dispatchers y cuál usas para qué.
Respuesta
Dispatchers.Main corre en el hilo de UI y es donde tocas vistas. Dispatchers.IO es un pool elástico grande para trabajo bloqueante — disco, red, base de datos — porque esos hilos pasan su tiempo esperando. Dispatchers.Default está dimensionado según la cantidad de CPUs y es para cómputo real como parsing u ordenamiento. Poner una llamada bloqueante en Default mata de hambre al pool, que es el error de dispatcher más común.
5
¿Cuál es la diferencia entre Flow, StateFlow y LiveData?
Respuesta
Flow es un stream asíncrono frío: empieza a producir cuando se colecta, y cada colector obtiene su propia ejecución. StateFlow es caliente y siempre tiene exactamente un valor actual, lo que lo hace el ajuste natural para estado de UI — los colectores nuevos obtienen inmediatamente el último estado. LiveData es el holder más viejo consciente del lifecycle; StateFlow más repeatOnLifecycle cubre la misma necesidad con el resto del toolkit de coroutines, así que el código nuevo generalmente lo prefiere.
6
¿Por qué un cambio de configuración recrea la Activity, y cómo sobrevives a eso?
Respuesta
Rotación, modo oscuro, idioma y cambios de tamaño de ventana destruyen y recrean la Activity para que pueda cargar recursos para la nueva configuración. El estado que debe sobrevivir pertenece a un ViewModel, que sobrevive la recreación. El estado que también debe sobrevivir la muerte del proceso va en SavedStateHandle, porque el sistema puede matar tu proceso en background y restaurar la tarea después — testear ese camino con "No mantener actividades" es lo que separa una app robusta de una que pierde el input del usuario.
7
¿Qué dispara la recomposición en Jetpack Compose, y qué la hace lenta?
Respuesta
Compose vuelve a correr un composable cuando un State que lee cambia, y salta composables cuyos parámetros son iguales y estables. La lentitud viene de parámetros inestables — una List plana o un lambda capturando un valor que cambia — que derrotan el skipping y vuelven a correr subárboles grandes. Los arreglos son elevar el estado al dueño común más bajo, usar colecciones inmutables o anotaciones @Immutable, y diferir lecturas con lambdas para que solo la fase de layout o draw vuelva a correr.
8
¿Por qué usar inyección de dependencias con Hilt en vez de construir objetos directamente?
Respuesta
DI elimina la necesidad de que una clase sepa cómo construir sus dependencias, lo que la hace testeable y mantiene el wiring en un lugar en vez de singletons esparcidos. Hilt genera el grafo de Dagger y ata los tiempos de vida de los componentes a los de Android — un @Singleton vive con la aplicación, un binding @ViewModelScoped con el ViewModel. El beneficio práctico es cambiar un repositorio real por uno falso en tests sin tocar código de producción.
9
¿Cómo estructuras una pantalla en términos de clean architecture?
Respuesta
La capa de UI tiene Compose o Views más un ViewModel que expone estado inmutable y recibe eventos. La capa de dominio tiene use cases y reglas de negocio puras sin imports de Android, que es lo que la hace testeable con JUnit plano. La capa de datos tiene repositorios que deciden entre Room y Retrofit y mapean DTOs a modelos de dominio. La regla que importa es la dirección de dependencia: el dominio no sabe nada de datos o UI.
10
¿Qué causa un ANR, y cómo encuentras uno después del release?
Respuesta
Un ANR se dispara cuando el hilo principal está bloqueado por aproximadamente cinco segundos en input, o límites más cortos para broadcasts y services. Las causas típicas son llamadas de base de datos o red en el hilo principal, un parseo JSON síncrono enorme, o contención de locks con un hilo de background. Después del release, los reportes de ANR de Play Console más el stack del hilo principal capturado apuntan al frame bloqueante; agregar StrictMode en builds de debug atrapa la mayoría de estos mucho antes de que se lancen.
11
¿Cómo evitas fugas de memoria en Android?
Respuesta
La causa recurrente es algo de larga vida sosteniendo una Activity o View — una referencia estática, un listener que nunca desregistraste, una clase interna con una referencia externa implícita, o una coroutine en GlobalScope. Las defensas son limitar el trabajo a un scope consciente del lifecycle, limpiar referencias de binding en onDestroyView para fragments, y usar el Context de la aplicación donde no se requiere un Context de Activity. LeakCanary en builds de debug atrapa el resto reportando el camino que retiene.
12
¿Qué son las scope functions y cuándo se lee mejor cada una?
Respuesta
let toma el receptor como argumento y devuelve el resultado del lambda, así que encaja en verificación de null y transformación. run hace lo mismo pero con this, útil para un bloque de llamadas que devuelve un valor. apply devuelve el receptor y es para configurar un objeto. also también devuelve el receptor pero lo toma como it, así que conviene para efectos secundarios como logging. with no es una extensión y solo agrupa llamadas en un objeto. Elegir por valor de retorno y forma del receptor es lo que las mantiene legibles en vez de ingeniosas.