Preguntas de entrevista Junior Ingeniero Móvil
En qué se centra una entrevista Junior de Ingeniero Móvil, las preguntas que enfrentarás y cómo practicarlas con feedback de IA al instante.
Qué se espera en el nivel Junior
Se esperan fundamentos de plataforma, UI básica y trabajo de funcionalidades guiado.
Preguntas de ejemplo para entrevista de Ingeniero Móvil
- TécnicaExplica el ciclo de vida de activity/view controller y los errores comunes.Lo que cubre una buena respuesta
- Ciclo de vida de Activity en Android: onCreate, onStart, onResume, onPause, onStop, onDestroy, onRestart
- Ciclo de vida de UIViewController en iOS: viewDidLoad, viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear, viewDidUnload
- Errores comunes: realizar operaciones pesadas en onCreate/viewDidLoad, no manejar correctamente la rotación, pérdida de estado al reanudar
- Uso incorrecto de singletons que mantienen referencias a Activities/ViewControllers causando memory leaks
- Falta de limpieza de observers (BroadcastReceiver, NotificationCenter) en onPause/viewWillDisappear
Ver respuesta de ejemplo
El ciclo de vida de una Activity (Android) comienza con onCreate(), donde se infla la UI y se inicializan datos; luego onStart() hace visible la actividad, onResume() obtiene el foco. Al girar el dispositivo, se pasa por onPause(), onStop() y onDestroy() antes de recrear. En iOS, UIViewController tiene viewDidLoad() (carga inicial), viewWillAppear() (justo antes de verse), viewDidAppear() (visible), viewWillDisappear() y viewDidDisappear(). Errores comunes: hacer llamadas de red en onCreate/viewDidLoad sin considerar rotaciones (se repite innecesariamente). Otro error: no cancelar corrutinas/hilos en onStop/viewWillDisappear, que continúan en background. También es frecuente mantener referencias a Activities/ViewControllers en objetos estáticos o singletons, impidiendo la recolección de basura. Para evitarlo, usar ViewModel (Android) o weak references (iOS). Finalmente, es crucial registrar/deregistrar BroadcastReceiver o NotificationCenter en onResume/onPause parejas para evitar fugas.
- Técnica¿Cómo mantienes fluida una lista larga con scroll en un dispositivo de gama baja?Lo que cubre una buena respuesta
- Virtualización de vistas: solo renderizar elementos visibles
- Reutilización de celdas (RecyclerView / UICollectionView)
- Reducir overdraw y uso de layout jerárquico plano
- Carga diferida de imágenes con tamaño adecuado y cache
- Evitar operaciones pesadas en hilo principal, usar AsyncTask/Coroutines/GCD
Ver respuesta de ejemplo
Para mantener una lista fluida en dispositivos de gama baja, es clave la virtualización: usar RecyclerView (Android) o UICollectionView/UITableView (iOS) que reciclan las celdas fuera de pantalla. Además, se debe aplanar la jerarquía de vistas para reducir overdraw; por ejemplo, usar ConstraintLayout en vez de múltiples layouts anidados. Las imágenes deben cargarse de forma diferida con bibliotecas como Glide o SDWebImage, que redimensionan y cachean en disco. Es fundamental ejecutar operaciones costosas (parseo, red) en hilos secundarios usando corrutinas, RxJava o GCD. También se recomienda usar DiffUtil (Android) o performBatchUpdates (iOS) para actualizaciones eficientes. Un error común es almacenar datos en objetos grandes dentro del ViewHolder; hay que mantenerlos ligeros. Finalmente, monitorear con Android Profiler o Instruments para detectar caídas de frames.
- Técnica¿Cómo diseñarías el soporte offline con sincronización posterior?Lo que cubre una buena respuesta
- Base de datos local SQLite/Room (Android) o CoreData (iOS)
- Cola de operaciones pendientes y estado de sincronización
- Estrategia de consistencia: last-write-wins o resolución de conflictos
- Notificaciones push para actualizaciones y sincronización en background
- Almacenamiento con límite de tamaño y prioridad de datos
Ver respuesta de ejemplo
Para soporte offline, primero se elige una base de datos local: Room (Android) con DAOs y entidades, o CoreData con NSManagedObject en iOS. Se almacenan tanto los datos mostrados como las operaciones pendientes en una cola (ej. tabla de sync). Cuando el usuario realiza una acción offline (ej. enviar mensaje), se guarda localmente y se añade a la cola con estado 'pendiente'. Al reconectar, un servicio en background (WorkManager/BackgroundTask) procesa la cola en orden FIFO, enviando cada operación al servidor. La resolución de conflictos puede ser last-write-wins con timestamps o un merge manual. Para datos de solo lectura, se pueden cachear en disco con límite de tamaño (LRU). Es importante notificar al usuario sobre el estado de sincronización mediante indicadores. Un error común es no manejar correctamente la concurrencia al actualizar la cola; se debe usar transacciones. También hay que considerar la autenticación: renovar tokens si es necesario antes de sincronizar.
- ProgramaciónImplementa una caché de imágenes con niveles de memoria y disco.Lo que cubre una buena respuesta
- Política LRU en memoria y disco con límites configurables
- Operaciones asíncronas con callbacks/corrutinas
- Redimensionamiento de imágenes antes de cachear
- Uso de referencia débil para evitar memory leaks
- Invalidación de caché por tiempo o notificaciones
Ver respuesta de ejemplo
Implementamos una caché de dos niveles: memoria (LRU HashMap o LinkedHashMap) y disco (directorio en almacenamiento interno). La interfaz principal tiene métodos get(key) y put(key, bitmap). Al solicitar una imagen, primero se busca en memoria; si falla, se busca en disco (leyendo como archivo y decodificando en hilo secundario). Si no está, se descarga de red, se redimensiona a un tamaño máximo (ej. 200dp) para ahorrar memoria, se almacena en disco y después en memoria. Para la caché en disco, usamos el nombre de archivo basado en hash de la URL. La caché en memoria tiene un tamaño máximo (ej. 1/8 de la memoria heap) y expulsa entradas viejas automáticamente. Además, debemos usar referencias débiles en los callbacks para evitar retener Activities. Un error común es decodificar imágenes en el hilo principal; siempre hacerlo en background. También hay que considerar que la caché en disco no sea ilimitada: implementamos un límite (ej. 50 MB) y eliminamos archivos LRU periódicamente.
Solución de referenciakotlin class ImageCache(private val context: Context, private val maxMemoryBytes: Long = Runtime.getRuntime().maxMemory() / 8) { private val memoryCache = object : LinkedHashMap<String, Bitmap>(0, 0.75f, true) { override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, Bitmap>?): Boolean { return size * 1024 * 1024 > maxMemoryBytes } } private val diskCacheDir = File(context.cacheDir, "images").apply { mkdirs() } private val diskCacheSizeBytes = 50 * 1024 * 1024L // 50 MB fun get(url: String, callback: (Bitmap?) -> Unit) { val key = url.hashCode().toString() memoryCache[key]?.let { callback(it); return } Thread { val file = File(diskCacheDir, key) if (file.exists()) { val bitmap = BitmapFactory.decodeFile(file.absolutePath) memoryCache[key] = bitmap callback(bitmap) } else { callback(null) } }.start() } fun put(url: String, bitmap: Bitmap) { val key = url.hashCode().toString() memoryCache[key] = bitmap Thread { val file = File(diskCacheDir, key) if (!file.exists()) { FileOutputStream(file).use { out -> bitmap.compress(Bitmap.CompressFormat.PNG, 100, out) } } // Mantener tamaño de disco bajo límite eliminando archivos viejos cleanupDiskIfNeeded() }.start() } private fun cleanupDiskIfNeeded() { val files = diskCacheDir.listFiles() ?: return var totalSize = files.sumOf { it.length() } if (totalSize > diskCacheSizeBytes) { files.sortedBy { it.lastModified() }.forEach { file -> file.delete() totalSize -= file.length() if (totalSize <= diskCacheSizeBytes) return } } } } - ProgramaciónConstruye una lista paginada que cargue más a medida que el usuario hace scroll.Lo que cubre una buena respuesta
- Paginación con offset y tamaño de página (limit)
- Detección de fin de scroll con ScrollListener / UICollectionView prefetch
- Cola de carga para evitar múltiples solicitudes simultáneas
- Manejo de estados: cargando, error, vacío, último elemento
- Cancelación de solicitudes al desmontar el fragmento
Ver respuesta de ejemplo
Implementamos una lista paginada con un endpoint REST que devuelve items paginados. En el ViewModel, mantenemos una lista mutable de items, un contador de página actual y un indicador de si hay más páginas. En el RecyclerView (Android) usamos un OnScrollListener que detecta cuándo el último elemento visible está cerca del final (ej. a 3 items del último) para disparar la carga de la siguiente página. En iOS, tableView(_:willDisplay:forRowAt:) o collectionView(_:willDisplay:forItemAt:) con prefetch. La solicitud se realiza en una corrutina (viewModelScope) y al recibir respuesta, se agregan los nuevos items a la lista y se notifica al adaptador. Debemos evitar múltiples cargas simultáneas mediante una variable flag isLoading. También manejamos estados: mostrar un footer cargando, un mensaje de error con reintento, y ocultar el footer cuando no hay más páginas. Un error común es olvidar cancelar la solicitud al salir de la pantalla, lo que puede causar fugas o actualizaciones en una Activity destruida. También hay que considerar la estabilidad del adaptador: usar DiffUtil para actualizaciones eficientes.
Solución de referenciakotlin class PaginatedViewModel : ViewModel() { private val _items = MutableLiveData<List<Item>>(emptyList()) val items: LiveData<List<Item>> = _items private var currentPage = 0 private var isLoading = false private var isLastPage = false fun loadNextPage() { if (isLoading || isLastPage) return isLoading = true viewModelScope.launch { try { val response = api.getItems(page = currentPage + 1, size = 20) val newItems = response.body()?.items ?: emptyList() if (newItems.isEmpty() || newItems.size < 20) isLastPage = true _items.value = _items.value + newItems currentPage++ } catch (e: Exception) { // Manejar error, mostrar en UI } finally { isLoading = false } } } } // En Activity/Fragment recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { val layoutManager = recyclerView.layoutManager as LinearLayoutManager val visibleItemCount = layoutManager.childCount val totalItemCount = layoutManager.itemCount val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() if (!viewModel.isLoading && !viewModel.isLastPage) { if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount - 3) { viewModel.loadNextPage() } } } }) - Diseño de sistemasDiseña una app de chat móvil que funcione offline y se sincronice al reconectarse.Lo que cubre una buena respuesta
- Base de datos local (Room/CoreData) para mensajes y contactos
- Cola de mensajes pendientes con confirmación de envío
- WebSocket persistente con reconexión exponencial
- Sincronización incremental con versionado de mensajes
- Notificaciones locales y badges para mensajes no leídos
Ver respuesta de ejemplo
Para una app de chat móvil offline-first, almacenamos todos los mensajes localmente en Room (Android) o CoreData. La entidad Mensaje tiene campos: id, chatId, texto, timestamp, estado (enviado, pendiente, error). Cuando el usuario envía un mensaje sin conexión, se guarda como 'pendiente' y se añade a una cola. Al reconectar, un WorkManager procesa la cola: envía cada mensaje vía WebSocket o REST y, tras confirmación del servidor, actualiza el estado a 'enviado'. Para recibir mensajes, mantenemos un WebSocket persistente con reconexión automática usando backoff exponencial. Cada mensaje tiene un ID único (UUID generado localmente) para evitar duplicados. La sincronización usa un timestamp de la última sincronización: al conectar, se piden mensajes desde ese timestamp. Los contactos y chats también se cachean localmente. Para notificar nuevos mensajes, usamos notificaciones locales. Es importante manejar conflictos: si un mensaje pendiente conflictúa con uno del servidor, se resuelve por orden de timestamp. Un error común: no considerar la rotación del dispositivo durante la sincronización; usar ViewModel y corrutinas con lifecycle. También hay que limitar el espacio de almacenamiento local eliminando mensajes antiguos.
- ConductualCuéntame de un fallo o bug de memoria difícil que rastreaste.Lo que cubre una buena respuesta
- Uso de Android Profiler / Instruments para detectar fugas
- Análisis de referencias con MAT / LeakCanary
- Síntoma: OutOfMemoryError o uso creciente de heap
- Caso concreto: retención de Activity por un hilo en background
- Solución: usar WeakReference o cancelar hilos en onDestroy
Ver respuesta de ejemplo
Un bug de memoria difícil que rastreé fue en una app de fotos: al navegar entre galerías, el heap crecía sin cesar hasta OutOfMemoryError. Usando Android Profiler vi que las Activities no se recolectaban. Con LeakCanary detecté que una clase Utils tenía un HashMap estático que almacenaba referencias a Listener con callback que contenía una Activity. Además, un hilo de procesamiento de imágenes en segundo plano no se cancelaba al salir de la Activity, reteniéndola. Para solucionarlo, cambié el HashMap a WeakHashMap y cancelé los hilos en onDestroy() usando un flag. También usé WeakReference en los callbacks. Otra vez, en iOS, un bloque (closure) que capturaba self accidentalmente causaba retención de ViewController. Se corrigió usando [weak self] en la closure. La lección es siempre revisar las referencias en closures y singletons, y usar herramientas como LeakCanary o Instruments para identificar fugas temprano.
- Conductual¿Cómo equilibras las nuevas funcionalidades con el tamaño y el rendimiento de la app?Lo que cubre una buena respuesta
- Minimizar dependencias y usar bibliotecas modulares
- Análisis de impacto de nuevas features en el tamaño del APK
- Priorizar funcionalidades por valor de negocio y frecuencia de uso
- Optimización de imágenes y recursos: vectorizados, compresión, WebP
- Pruebas de rendimiento y A/B testing para medir impacto
Ver respuesta de ejemplo
Para equilibrar nuevas funcionalidades con tamaño y rendimiento, adopto un enfoque modular: cada feature es un módulo que se puede incluir condicionalmente (por ejemplo, con feature flags). Antes de agregar una biblioteca nueva, evaluamos su tamaño (ej. MDC vs diseño propio). Usamos ProGuard/R8 para ofuscar y eliminar código no usado. Las imágenes se optimizan con WebP y vectorizados; los recursos grandes se descargan bajo demanda. Realizamos análisis de rendimiento con Android Vitals o MetricKit para detectar regresiones. Priorizamos features según su uso real (analytics) y las implementamos primero a través de feature flags para medir impacto en tamaño de APK, tiempos de inicio y memoria. Si una feature aumenta significativamente el tamaño, se puede ofrecer como módulo descargable (Android App Bundles/Dynamic Features). También mantenemos un presupuesto de rendimiento: cada nueva feature no debe añadir más de X ms al inicio ni más de Y MB al APK. Un error común es no medir el impacto acumulativo; hacemos revisiones periódicas de caché y optimizamos bases de datos locales (índices).
Qué evalúan los entrevistadores
Profundidad en plataforma
Ciclo de vida, hilos y memoria en iOS (Swift) o Android (Kotlin).
UI y rendimiento
Listas fluidas, renderizado, tirones y consideraciones de batería.
Arquitectura de la app
MVVM/MVI, navegación, inyección de dependencias y modularidad.
Offline y sincronización
Almacenamiento local, caché, resolución de conflictos y pérdida de conectividad.
Ingeniería de releases
Builds para la tienda, reporte de fallos y despliegues graduales.
Cómo prepararte
- Empieza por el ciclo de vida y la memoria: son los puntos de fallo más comunes.
- Habla de forma proactiva del offline y las redes inestables; los entrevistadores de móvil lo esperan.
- Demuestra que mides: el perfilado, las tasas de fallo y el tiempo de fotograma superan a las generalidades.
Preguntas frecuentes
¿Las entrevistas de móvil incluyen programación de estructuras de datos?
A menudo sí, además de programación específica de la plataforma como construir una caché o una lista paginada, y discusión de arquitectura de la app.
¿Qué preguntas de arquitectura aparecen para puestos móviles?
Espera MVVM/MVI, navegación, inyección de dependencias y el diseño de apps capaces de funcionar offline con sincronización.
¿Cómo debo prepararme para una entrevista de ingeniería móvil?
Repasa el ciclo de vida y la memoria de la plataforma, construye una pequeña funcionalidad desde cero y ensaya decisiones de arquitectura en entrevistas simuladas.
Practica preguntas de Ingeniero Móvil con feedback instantáneo de IA
Offersly realiza una entrevista simulada adaptada a tu currículum y al puesto objetivo, y luego puntúa cada respuesta por relevancia, profundidad, claridad y corrección.