Preguntas de entrevista Junior Ingeniero Backend
En qué se centra una entrevista Junior de Ingeniero Backend, las preguntas que enfrentarás y cómo practicarlas con feedback de IA al instante.
Qué se espera en el nivel Junior
Se esperan APIs CRUD, SQL básico, estructuras de datos clave y tareas bien delimitadas.
Preguntas de ejemplo para entrevista de Ingeniero Backend
- Técnica¿Cuándo elegirías una base de datos SQL sobre un almacén NoSQL, y por qué?Lo que cubre una buena respuesta
- Integridad relacional y esquema estricto
- Soporte ACID y transacciones
- Consultas complejas con JOIN
- Escalabilidad vertical vs horizontal
- Casos de uso: finanzas, ERP, datos estructurados
Ver respuesta de ejemplo
Elegirías una base de datos SQL cuando necesitas integridad relacional, es decir, cuando los datos tienen relaciones bien definidas y requieres cumplir con restricciones de clave foránea. SQL proporciona transacciones ACID, lo que es crucial en aplicaciones financieras o de inventario donde la consistencia es prioritaria. Las consultas complejas con JOINs se ejecutan eficientemente gracias al optimizador de consultas relacional. En cambio, NoSQL es preferible cuando se necesita escalabilidad horizontal masiva, esquemas flexibles o manejo de grandes volúmenes de datos no estructurados (como documentos JSON). El principal tradeoff es que SQL sacrifica escalabilidad horizontal y flexibilidad de esquema a cambio de consistencia fuerte y soporte de joins. Un error común es elegir NoSQL cuando en realidad los datos son altamente relacionales, lo que termina en implementaciones complejas de consistencia.
- Técnica¿Cómo funcionan los índices de base de datos y cuáles son sus compromisos?Lo que cubre una buena respuesta
- Estructura de datos subyacente (B-tree, hash)
- Reducción de costo de búsqueda (O(log n) vs O(n))
- Costo en escritura por mantenimiento del índice
- Almacenamiento adicional (espacio en disco)
- Tipos: agrupado (clustered) vs no agrupado (non-clustered)
Ver respuesta de ejemplo
Los índices de base de datos son estructuras auxiliares que permiten localizar filas rápidamente sin escanear toda la tabla. Comúnmente se implementan con árboles B+ (para rangos) o tablas hash (para igualdad). El compromiso principal es acelerar las lecturas (O(log n) para B-tree) al costo de ralentizar las escrituras (INSERT/UPDATE/DELETE) porque cada modificación debe actualizar también el índice. Además, ocupan espacio adicional en disco, potencialmente mayor que la tabla misma. Un índice agrupado ordena físicamente los datos (solo uno por tabla), mientras que un no agrupado apunta a las filas. Una buena práctica es indexar columnas usadas en WHERE y JOIN, pero evitar índices innecesarios en tablas con muchas escrituras. Un error común es crear demasiados índices, lo que degrada el rendimiento de escritura sin un beneficio claro en lecturas.
- TécnicaExplica cómo harías idempotente un endpoint de pagos.Lo que cubre una buena respuesta
- Idempotency-Key en cabecera HTTP
- Almacenamiento del resultado de la operación
- Deduplicación por clave en base de datos
- Manejo de concurrencia con bloqueo optimista o pesimista
- Devolver mismo resultado en retry
Ver respuesta de ejemplo
Para hacer idempotente un endpoint de pagos, se debe requerir que el cliente envíe una clave de idempotencia única por operación (por ejemplo, en el header Idempotency-Key). El servidor, al recibir la solicitud, verifica si esa clave ya fue procesada; si lo fue, devuelve la respuesta almacenada sin ejecutar el pago de nuevo. Si no, inicia una transacción: primero registra la clave (con estado 'pendiente'), luego procesa el pago (por ejemplo, debitando la cuenta) y finalmente actualiza el registro con el resultado. Es crucial usar un índice único sobre la clave de idempotencia para evitar duplicados. Para manejar concurrencia, se puede usar bloqueo optimista (versión) o pesimista (SELECT FOR UPDATE). Un error común es no considerar que la respuesta puede perderse y el reintento intente crear un pago duplicado; la idempotencia previene eso. Además, se debe asegurar que la lógica de negocio sea atómica: si falla el pago, la clave se marca como 'fallida' para que reintentos no reintenten pagar.
- ProgramaciónImplementa una caché LRU con get y put en O(1).Lo que cubre una buena respuesta
- Diccionario hash (dict) para acceso O(1)
- Lista doblemente enlazada para orden de uso
- Get: mover nodo al frente
- Put: agregar nuevo o actualizar, y eliminar LRU si se excede capacidad
- Uso de nodos con clave, valor y punteros prev/next
Ver respuesta de ejemplo
Para implementar una caché LRU con operaciones O(1), se combina un diccionario hash (que mapea clave a un nodo) con una lista doblemente enlazada que mantiene el orden de uso. El nodo más recientemente usado estará al frente (head) y el menos usado al final (tail). En get(key), si la clave existe, se mueve el nodo correspondiente al frente y se devuelve su valor. En put(key, value), si la clave ya existe, se actualiza el valor y se mueve el nodo al frente; si no, se crea un nuevo nodo, se inserta al frente y se agrega al diccionario; si se excede la capacidad, se elimina el nodo del tail (LRU) y se borra del diccionario. La lista doblemente enlazada permite inserción y eliminación en O(1). La complejidad temporal es O(1) para get y put, y la espacial es O(capacidad). Un error común es no actualizar correctamente los punteros al mover nodos; una implementación cuidadosa con métodos auxiliares remove y addToFront lo resuelve.
Solución de referenciapython class Node: def __init__(self, key, value): self.key = key self.value = value self.prev = None self.next = None class LRUCache: def __init__(self, capacity: int): self.capacity = capacity self.cache = {} # diccionario clave -> nodo # Nodos centinela para evitar comprobaciones de nulos self.head = Node(0, 0) # más reciente self.tail = Node(0, 0) # menos reciente self.head.next = self.tail self.tail.prev = self.head def _remove(self, node): # Elimina un nodo de la lista enlazada prev = node.prev next = node.next prev.next = next next.prev = prev def _add_to_front(self, node): # Añade un nodo justo después del head node.prev = self.head node.next = self.head.next self.head.next.prev = node self.head.next = node def get(self, key: int) -> int: if key in self.cache: node = self.cache[key] self._remove(node) self._add_to_front(node) return node.value return -1 def put(self, key: int, value: int) -> None: if key in self.cache: node = self.cache[key] node.value = value self._remove(node) self._add_to_front(node) else: if len(self.cache) >= self.capacity: # Eliminar nodo LRU (justo antes de tail) lru = self.tail.prev self._remove(lru) del self.cache[lru.key] nuevo = Node(key, value) self.cache[key] = nuevo self._add_to_front(nuevo) - ProgramaciónDada una secuencia de eventos, diseña un limitador de tasa para una API.Lo que cubre una buena respuesta
- Algoritmo de token bucket o sliding window
- Identificación por usuario/cliente
- Almacenamiento en Redis con expiración
- Límite de requests por ventana de tiempo
- Respuesta 429 Too Many Requests y cabeceras
Ver respuesta de ejemplo
Para diseñar un limitador de tasa para una API basado en una secuencia de eventos, se puede usar el algoritmo de token bucket o una ventana deslizante. En token bucket, cada usuario tiene un bucket que se llena a una tasa constante (tokens por segundo) con un máximo de tokens. Cada request consume un token; si no hay tokens, se rechaza con 429. La ventana deslizante utiliza un contador por intervalo de tiempo (ej. 1 segundo) que se almacena en Redis con clave por usuario y timestamp redondeado. Al llegar un request, se incrementa el contador y si excede el límite se rechaza. Se debe incluir cabeceras como X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset. Para sistemas distribuidos, usar Redis con transacciones o Lua scripting para atomicidad. Un error común es usar una ventana fija que no maneje picos al final del intervalo; la ventana deslizante lo suaviza. La complejidad de cada decisión es O(1) en memoria (por usuario).
- Diseño de sistemasDiseña un acortador de URLs que maneje miles de millones de redirecciones.Lo que cubre una buena respuesta
- Generación de claves únicas (base62 con contador o hash)
- Almacenamiento: BD relacional (id, clave, url original)
- Redirección 301/302
- Escalabilidad: caché en CDN, balanceo de carga
- Manejo de miles de millones con sharding y replicación
Ver respuesta de ejemplo
Para diseñar un acortador de URLs que maneje miles de millones de redirecciones, primero se definen los requisitos: generar claves cortas únicas (ej. 7 caracteres base62), almacenar la URL original, redirigir rápidamente y soportar alta concurrencia. Se usa un cuello de botella de generación de claves: un contador incremental en una base de datos (o un servicio de generación de IDs como Snowflake) que se codifica en base62 para obtener una clave. La base de datos principal puede ser relacional (PostgreSQL, MySQL) con un índice único en la clave para búsquedas O(log n). Para escalar, se aplica sharding por la clave (ej. consistencia hash) y replicación para alta disponibilidad. La redirección se realiza mediante una respuesta HTTP 301 (permanente) o 302 (temporal), y para reducir latencia se utiliza una caché en memoria (Redis) o CDN para las URLs más populares. Un error común es generar claves aleatorias sin verificar unicidad, lo que requiere comprobaciones costosas. La robustez se logra mediante almacenamiento persistente y balanceadores de carga distribuyendo tráfico entre servicios de redirección sin estado.
- ConductualDescribe una caída del servicio que ayudaste a resolver y qué cambiaste después.Lo que cubre una buena respuesta
- Síntoma y alcance de la caída
- Detección mediante monitoreo y alertas
- Análisis de causa raíz (ej. bug en deploy, escalado insuficiente)
- Mitigación: rollback, escalado manual
- Cambios post-mortem: pruebas automatizadas, límites de recurso, circuit breakers
Ver respuesta de ejemplo
En una experiencia pasada, ayudé a resolver una caída del servicio de autenticación que afectó a todos los usuarios. El síntoma fue que las solicitudes de login fallaban con timeout. Detectamos el problema mediante alertas de latencia elevada en Grafana y aumento de errores 500. La causa raíz fue un cambio reciente en la lógica de validación de tokens que introducía un bucle infinito en ciertos casos, saturando los hilos del servidor. Mitigamos realizando un rollback inmediato a la versión anterior, lo que restauró el servicio en minutos. Después, implementamos cambios: añadimos pruebas unitarias y de integración que cubrían ese caso extremo, establecimos límites de concurrencia (circuit breaker) para evitar sobrecarga, y mejoramos el monitoreo con métricas específicas de latencia por endpoint. También creamos un runbook para incidentes similares. Un error común es enfocarse solo en la solución inmediata sin prevenir recurrencias; los cambios post-mortem son cruciales.
- ConductualCuéntame de una vez que tuviste que hacer un compromiso difícil de consistencia de datos.Lo que cubre una buena respuesta
- Contexto: sistema de feeds con consistencia eventual
- Conflicto entre baja latencia y consistencia fuerte
- Decisión: aceptar lectura de datos desactualizados
- Compromiso: usar caché con expiración corta
- Resultado: mejora de experiencia de usuario con riesgo mínimo
Ver respuesta de ejemplo
En una aplicación de red social, necesitábamos mostrar el feed de publicaciones de amigos. Inicialmente usábamos consistencia fuerte: cada vez que se publicaba, invalidábamos la caché de todos los seguidores, causando alta latencia y picos de carga. El equipo discutió si mantener consistencia fuerte o pasar a eventual consistency. Decidimos comprometernos aceptando lectura de datos ligeramente desactualizados (unos segundos) a cambio de una latencia mucho menor. Implementamos un sistema de caché con expiración corta (10 segundos) y una cola de mensajes para propagar las actualizaciones asíncronamente. Así, los usuarios veían cambios casi en tiempo real, pero sin la carga de invalidaciones inmediatas. El compromiso fue documentado y aceptado por stakeholders: en caso de publicación, el seguidor podría no verla instantáneamente, pero la mayoría de las veces no notaba el retraso. Medimos que la latencia del feed se redujo un 80% y la consistencia eventual no generó quejas significativas. Un error común es no establecer expiraciones de caché adecuadas, llevando a datos obsoletos prolongados.
Qué evalúan los entrevistadores
Modelado de datos
Relacional vs. NoSQL, indexación, normalización y transacciones.
Diseño de APIs
REST/GraphQL/gRPC, idempotencia, paginación y versionado.
Concurrencia
Bloqueos, condiciones de carrera, colas y consistencia eventual.
Diseño de sistemas
Caché, sharding, replicación y manejo de fallos a escala.
Algoritmos
Análisis de complejidad y elección práctica de estructuras de datos.
Cómo prepararte
- Expón tus supuestos y restricciones antes de diseñar: los entrevistadores premian la delimitación del alcance.
- Analiza siempre la complejidad temporal y espacial en las respuestas de programación.
- En diseño de sistemas, lidera la conversación: requisitos, API, modelo de datos, escala y modos de fallo.
Preguntas frecuentes
¿Qué preguntas de diseño de sistemas aparecen en entrevistas de backend?
Las consignas comunes incluyen diseñar un acortador de URLs, un limitador de tasa, un feed de noticias o un sistema de chat, con discusión de caché, sharding y consistencia.
¿Cuánto conocimiento de algoritmos requieren las entrevistas de backend?
La mayoría de las empresas todavía incluyen una o dos rondas de código centradas en estructuras de datos y complejidad, aunque el trabajo diario sea más de sistemas.
¿Cómo practico entrevistas de backend de forma efectiva?
Combina ejercicios de código con práctica hablada de diseño de sistemas, y haz entrevistas simuladas para defender los compromisos en voz alta.
Practica preguntas de Ingeniero Backend 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.