Guía de entrevistas para Ingeniero Frontend
Las entrevistas de frontend evalúan más que datos sueltos sobre frameworks: examinan cómo razonas sobre el navegador, estructuras el estado de la UI y entregas interfaces accesibles y de buen rendimiento. Las preguntas siguientes reflejan lo que los equipos realmente preguntan, desde el funcionamiento interno del renderizado hasta la construcción práctica de componentes.
Qué evalúan los entrevistadores
Fundamentos de JavaScript
Closures, el event loop, promesas/async, prototipos y el enlace de `this`.
Profundidad en frameworks
Renderizado, reconciliación, hooks y gestión de estado en React (o Vue/Angular).
CSS y maquetación
Flexbox/grid, la cascada, contextos de apilamiento y diseño responsive.
Rendimiento
Ruta crítica de renderizado, tamaño del bundle, carga diferida y Core Web Vitals.
Accesibilidad
HTML semántico, ARIA, navegación por teclado y soporte para lectores de pantalla.
Preguntas de ejemplo para entrevista de Ingeniero Frontend
- TécnicaExplica el event loop del navegador y en qué se diferencian las microtareas de las macrotareas.Lo que cubre una buena respuesta
- Definición de event loop y su rol en concurrencia
- Cola de macrotareas vs microtareas
- Prioridad de ejecución: microtareas antes que macrotareas
- Ejemplos típicos: setTimeout (macro), Promesas (micro)
- Impacto en renderizado y UI responsiveness
Ver respuesta de ejemplo
El event loop es el mecanismo que permite al navegador manejar operaciones asíncronas en un solo hilo. Consiste en un bucle que monitorea la pila de ejecución y las colas de tareas. Las macrotareas incluyen eventos como setTimeout, setInterval, o eventos del DOM y se colocan en una cola de macrotareas. Las microtareas, como las promesas y MutationObserver, se colocan en una cola separada de microtareas. La diferencia clave es que, antes de procesar la siguiente macrotarea, el event loop vacía toda la cola de microtareas. Esto significa que las microtareas siempre se ejecutan antes que las macrotareas pendientes. Por ejemplo, una promesa resuelta se ejecutará inmediatamente después del código síncrono actual, incluso si hay un setTimeout pendiente. Esto es crucial para evitar bloqueos y garantizar respuestas rápidas en la UI. Un error común es asumir que setTimeout(fn, 0) ejecuta instantáneamente, pero en realidad se convierte en macrotarea y puede ser retrasado por microtareas.
- Técnica¿Qué provoca un re-renderizado en React y cómo evitas los innecesarios?Lo que cubre una buena respuesta
- Cambios en estado o props desencadenan re-render
- Re-render de componente padre propaga a hijos
- Uso de React.memo para evitar renders innecesarios
- useCallback y useMemo para estabilizar referencias
- Claves (key) en listas para evitar reconciliación costosa
Ver respuesta de ejemplo
En React, un re-render se provoca principalmente cuando cambia el estado local (useState) o las props recibidas de un componente padre. Además, el contexto y las fuerzas de actualización como forceUpdate también lo inician. Por defecto, cuando un componente padre se re-renderiza, todos sus hijos se re-renderizan, incluso si sus props no cambian. Para evitar re-renders innecesarios, se usa React.memo en componentes funcionales, que realiza una comparación superficial de props. También se emplean useCallback para memoizar funciones y useMemo para valores calculados, evitando que nuevas referencias provoquen re-render en componentes hijos memoizados. Es importante asignar keys únicas y estables a elementos en listas para que React pueda identificar cambios eficientemente. Un error común es usar índices como key, lo que puede causar problemas de estado y rendimiento. Finalmente, evitar crear objetos o arrays nuevos en cada render sin necesidad, ya que rompen la comparación superficial de memo.
- TécnicaDescribe cómo la cascada de CSS resuelve reglas en conflicto.Lo que cubre una buena respuesta
- Origen de la hoja de estilos: user agent, usuario, autor
- Especificidad: ID > clase > etiqueta
- Importancia: !important invierte la cascada
- Orden de aparición: último gana si igual especificidad
- Herencia y valor inicial
Ver respuesta de ejemplo
La cascada de CSS es el algoritmo que determina qué reglas se aplican cuando hay conflictos. Primero se considera el origen: las reglas del autor (CSS del desarrollador) tienen prioridad sobre las del usuario y las del user agent. Luego se evalúa la especificidad: un selector de ID (100) supera a una clase (010) y esta a una etiqueta (001). Si la especificidad es igual, gana la regla que aparece al final en el orden de carga. La declaración !important eleva la prioridad de una regla, pero su uso excesivo complica el mantenimiento. También importa si la propiedad es heredable; por ejemplo, color se hereda, pero margin no. El valor inicial definido en la especificación se usa si no hay ninguna regla aplicable. Un error frecuente es no entender que la especificidad se calcula con un sistema de pesos, no solo contando selectores. Herramientas como las DevTools del navegador son útiles para depurar la cascada.
- ProgramaciónImplementa una función debounce y explica cuándo la usarías.Lo que cubre una buena respuesta
- Debounce retrasa la ejecución hasta un período de inactividad
- Parámetros: función, tiempo de espera, opciones leading/trailing
- Uso típico: eventos de input, búsqueda, redimensionamiento
- Diferencia con throttle: limitación de frecuencia
- Implementación con temporizador y clausura
Ver respuesta de ejemplo
La función debounce retrasa la ejecución de una función hasta que haya transcurrido un tiempo de espera sin que se vuelva a invocar. Es útil en escenarios donde las llamadas se disparan con alta frecuencia, como en la búsqueda en un campo de texto, eventos de redimensionamiento o scroll. La implementación típica usa un temporizador que se reinicia en cada llamada, de modo que solo se ejecuta la última llamada después del período de silencio. Se puede configurar para que ejecute inmediatamente en el primer evento (leading) o al final (trailing). La función debounce retorna una nueva función que encapsula el comportamiento y debe usarse con cuidado respecto al contexto 'this' y los argumentos. Una variante común incluye un método cancel para limpiar el temporizador. Es importante diferenciar debounce de throttle: mientras debounce agrupa ráfagas en una sola ejecución al final, throttle garantiza una ejecución cada cierto intervalo.
Solución de referenciajavascript function debounce(func, wait, immediate = false) { let timeout = null; let context, args; function debounced(...args) { context = this; args = args; const callNow = immediate && !timeout; if (timeout) clearTimeout(timeout); timeout = setTimeout(() => { timeout = null; if (!immediate) func.apply(context, args); }, wait); if (callNow) func.apply(context, args); } debounced.cancel = function() { if (timeout) clearTimeout(timeout); timeout = null; }; return debounced; } - ProgramaciónConstruye un componente de autocompletado accesible con soporte de teclado.Lo que cubre una buena respuesta
- Uso de ARIA: role='combobox', aria-autocomplete, aria-expanded
- Manejo de teclado: flechas, Enter, Escape, lista de opciones
- Debounce para consulta mientras se escribe
- Estado de carga, resultados vacíos y selección
- Virtualización si muchas opciones
Ver respuesta de ejemplo
El componente de autocompletado debe ser accesible siguiendo el patrón de combobox de ARIA. El input tiene role='combobox', aria-autocomplete='list', y aria-expanded para indicar si la lista está visible. La lista de sugerencias tiene role='listbox' y cada opción role='option'. El manejo de teclado incluye flechas arriba/abajo para navegar, Enter para seleccionar, Escape para cerrar, y se mantiene el foco en el input. La funcionalidad de búsqueda se implementa con un debounce para evitar consultas excesivas mientras el usuario escribe. Se manejan estados como carga, resultados vacíos y selección. La navegación por teclado desplaza la lista con scrollIntoView para mantener visible la opción activa. Es importante también considerar la virtualización si hay muchas opciones para no degradar el rendimiento. Un error común es no limpiar el timer en desmontaje o no manejar correctamente el foco cuando la lista se cierra.
Solución de referenciajsx import React, { useState, useRef, useEffect } from 'react'; const Autocomplete = ({ suggestions }) => { const [inputValue, setInputValue] = useState(''); const [filteredSug, setFilteredSug] = useState([]); const [isOpen, setIsOpen] = useState(false); const [activeIndex, setActiveIndex] = useState(-1); const inputRef = useRef(null); const listRef = useRef(null); const debounceTimer = useRef(null); const filterSuggestions = (value) => { clearTimeout(debounceTimer.current); debounceTimer.current = setTimeout(() => { if (value.length > 0) { const filtered = suggestions.filter(s => s.toLowerCase().includes(value.toLowerCase()) ); setFilteredSug(filtered); setIsOpen(filtered.length > 0); setActiveIndex(-1); } else { setFilteredSug([]); setIsOpen(false); } }, 300); }; const handleInputChange = (e) => { const value = e.target.value; setInputValue(value); filterSuggestions(value); }; const handleKeyDown = (e) => { if (!isOpen) return; switch (e.key) { case 'ArrowDown': e.preventDefault(); setActiveIndex(prev => (prev < filteredSug.length - 1 ? prev + 1 : 0)); break; case 'ArrowUp': e.preventDefault(); setActiveIndex(prev => (prev > 0 ? prev - 1 : filteredSug.length - 1)); break; case 'Enter': if (activeIndex >= 0) { setInputValue(filteredSug[activeIndex]); setIsOpen(false); setFilteredSug([]); } break; case 'Escape': setIsOpen(false); break; } }; const handleSuggestionClick = (suggestion) => { setInputValue(suggestion); setIsOpen(false); setFilteredSug([]); inputRef.current.focus(); }; useEffect(() => { if (listRef.current && activeIndex >= 0) { const activeItem = listRef.current.children[activeIndex]; if (activeItem) activeItem.scrollIntoView({ block: 'nearest' }); } }, [activeIndex]); return ( <div style={{ position: 'relative' }}> <input ref={inputRef} type="text" role="combobox" aria-autocomplete="list" aria-expanded={isOpen} aria-controls="autocomplete-list" aria-activedescendant={activeIndex >= 0 ? `option-${activeIndex}` : undefined} value={inputValue} onChange={handleInputChange} onKeyDown={handleKeyDown} placeholder="Buscar..." /> {isOpen && ( <ul id="autocomplete-list" ref={listRef} role="listbox" style={{ position: 'absolute', listStyle: 'none', padding: 0, margin: 0, border: '1px solid black', maxHeight: '200px', overflowY: 'auto' }} > {filteredSug.map((suggestion, index) => ( <li key={suggestion} id={`option-${index}`} role="option" aria-selected={index === activeIndex} onClick={() => handleSuggestionClick(suggestion)} style={{ backgroundColor: index === activeIndex ? 'lightblue' : 'white', padding: '5px', cursor: 'pointer' }} > {suggestion} </li> ))} </ul> )} </div> ); }; export default Autocomplete; - Diseño de sistemasDiseña la arquitectura frontend de un editor de documentos colaborativo en tiempo real.Lo que cubre una buena respuesta
- Requisitos: baja latencia, consistencia, colaboración concurrente
- Componentes: WebSocket para comunicación, CRDT/OT para conflicto
- Modelo de datos: documento como lista de operaciones o árbol
- Flujo: cursor local, sincronización y reconciliación
- Escalabilidad: sharding, servidores de transformación
Ver respuesta de ejemplo
La arquitectura frontend de un editor colaborativo en tiempo real debe priorizar la baja latencia y la consistencia. Se utiliza WebSocket para una comunicación bidireccional persistente. Para manejar ediciones concurrentes, se emplea CRDT (Tipos de Datos Replicados Sin Conflictos) o OT (Transformación Operacional). En el cliente, el documento se representa como un modelo interno (por ejemplo, un árbol de operaciones) y se actualiza localmente de forma optimista mientras se envía la operación al servidor. El servidor coordina la aplicación de operaciones y resuelve conflictos. La interfaz muestra cursores de otros usuarios y aplica transformaciones para mantener la vista consistente. Para escalar, se puede particionar el documento en fragmentos y usar un servicio de transformación distribuido. Es crucial manejar desconexiones con colas de reenvío y retrasar la actualización de la UI hasta confirmar la operación. Un error común es no considerar la sincronización de estados de selección y formato enriquecido.
- ConductualCuéntame de una vez que mejoraste el rendimiento de una página lenta.Lo que cubre una buena respuesta
- Identificar cuellos de botella con Lighthouse y DevTools
- Medir métricas: LCP, FID, CLS
- Optimizaciones: lazy loading, code splitting, compresión
- Uso de CDN y caché del navegador
- Resultado: mejora cuantificable (ej. reducción de 5s a 1.5s)
Ver respuesta de ejemplo
En una ocasión, trabajé en una tienda online que tenía un LCP de 5 segundos. Usando Lighthouse y las DevTools, identificamos que el bundle de JavaScript era muy grande y que las imágenes no estaban optimizadas. Implementamos lazy loading para las imágenes y componentes que no estaban en el viewport inicial. Dividimos el código con React.lazy y Suspense para cargar bajo demanda, reduciendo el bundle inicial en un 60%. También configuramos compresión Gzip en el servidor y movimos recursos estáticos a un CDN. Aplicamos precarga de fuentes críticas y optimizamos las dependencias. Como resultado, el LCP bajó a 1.5 segundos y la puntuación de rendimiento subió de 35 a 85. El proceso implicó medir, iterar y validar los cambios con pruebas A/B. Lo aprendido es que las ganancias más grandes suelen venir de reducir el JavaScript inicial y optimizar las imágenes.
- Conductual¿Cómo manejas los desacuerdos con los diseñadores sobre compromisos de UX?Lo que cubre una buena respuesta
- Empatía: entender la perspectiva del diseñador, basada en datos
- Comunicación: plantear tradeoffs con datos concretos
- Compromiso: buscar soluciones que satisfagan ambas partes
- Ejemplo: reducir animaciones para mejorar rendimiento
- Seguimiento: revisar después de implementar y ajustar
Ver respuesta de ejemplo
Cuando tengo desacuerdos con diseñadores, primero busco entender su motivación: ¿es por consistencia, marca o experiencia de usuario? Luego presento datos cuantitativos (por ejemplo, tiempos de carga, tasas de conversión) y cualitativos (test de usabilidad) para explicar las limitaciones técnicas. Por ejemplo, si una animación compleja causa jank, propongo una versión simplificada que mantenga la esencia visual pero sea más eficiente. Buscamos un compromiso donde ambas partes cedan, como implementar la animación solo en dispositivos de alto rendimiento. Después de implementar, hacemos un seguimiento con métricas para validar la decisión. Es fundamental mantener una comunicación respetuosa y enfocada en el objetivo común: la mejor experiencia para el usuario. Un error es imponer sin argumentos; en cambio, la colaboración genera mejores resultados.
Qué cambia según el nivel
- Junior
- Se esperan fundamentos: DOM, HTML semántico, JS básico y trabajo con componentes con indicaciones guiadas.
- Intermedio
- Se espera profundidad en frameworks, gestión de estado, nociones de rendimiento y entrega autónoma de funcionalidades.
- Senior
- Se espera arquitectura, presupuestos de rendimiento, responsabilidad sobre la accesibilidad y señales de mentoría o liderazgo técnico.
Cómo prepararte
- Explica en voz alta la ruta de renderizado: los entrevistadores puntúan tu razonamiento, no solo la respuesta.
- Menciona siempre la accesibilidad y el rendimiento, aunque no te lo pregunten explícitamente.
- Practica construir un componente de principio a fin sin el autocompletado del IDE.
Preguntas frecuentes
¿Qué preguntas de programación son comunes en entrevistas de frontend?
Espera manipulación del DOM, funciones de utilidad como debounce/throttle y la construcción de un pequeño componente interactivo como un autocompletado o un modal.
¿Las entrevistas de frontend incluyen diseño de sistemas?
En niveles intermedio y senior, sí: normalmente diseño centrado en el frontend, como un feed, un sistema de diseño o un editor colaborativo, en lugar de infraestructura de backend.
¿Cómo me preparo rápido para una entrevista de frontend?
Practica los fundamentos de JavaScript, construye algunos componentes desde cero y haz entrevistas simuladas cronometradas para poder explicar tu razonamiento bajo presión.
Practica preguntas de Ingeniero Frontend 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.