Entrevista Node.js: resuelve bloqueos, errores asíncronos y límites de concurrencia

Prepara tu entrevista Node.js con HTTP local: limita tareas antes de iniciarlas, conserva el orden y explica fallos, reintentos y cancelación con pruebas.

Author: PracHub

Published: 10/11/2026

Entrevista Node.js: resuelve bloqueos, errores asíncronos y límites de concurrencia

October 11, 2026

Quick Overview

Caso originalNode26.9.0ESM dosservidoresHTTPloopback/38checks. Cap2porlote, completanB-C-D-A/salidaA-B-C-D; doslotes4activas. B503stopadmisiónC/D, drainA->502, efectoA1; retryA2. FetchAbortError/cierrecliente/servidorcontinúacontador1. SinDBduradera/rollback/transacción/rateglobal/Workers/benchmark.

Backend EngineerFree

Una entrevista de Node.js puede empezar con «¿por qué tarda este endpoint?» y terminar preguntando qué ocurre si una petición falla mientras otras siguen trabajando. Para responder, separa tres decisiones: dónde se ejecuta el cálculo, cuántas operaciones pueden estar en vuelo y qué significa devolver un error cuando ya existen efectos parciales.

Este ejercicio original usa un endpoint de importación sintético y cuatro tareas, A, B, C y D. Puedes seguir cuándo empieza cada petición HTTP local, qué respuesta recibe y qué efecto queda después de un fallo. La preparación general de Node.js en PracHub amplía después hacia streams, memoria y diagnóstico del proceso.

Dos lotes con concurrencia dos pueden mantener cuatro tareas en vuelo en total

Define el contrato antes de elegir Promise.all

Nuestro endpoint POST /import recibe hasta cuatro identificadores y un límite. Consulta otro servidor local y devuelve los resultados en el orden de entrada. Si observa un rechazo, deja de admitir tareas nuevas, espera las ya iniciadas y responde con el error del lote.

Resultado observado: el laboratorio ejecutó 38 comprobaciones con Node.js 26.9.0 y módulos ESM. Ambos servidores HTTP escucharon únicamente en loopback y puertos temporales. Las compuertas del ensayo controlan cuándo responde cada tarea; el orden no depende de adivinar retrasos en milisegundos.

Los efectos del servidor dependiente son contadores sintéticos en memoria. No representan registros duraderos, pagos ni una transacción de base de datos. Tampoco se midió carga de producción. Relatos de candidatos: no usamos testimonios para afirmar rondas, duración ni preguntas futuras de una empresa. Las preguntas de PracHub son material de práctica.

Recomendación de preparación: escribe primero el contrato de éxito y de error. «Hacer todo en paralelo» no decide cómo ordenar salidas, qué trabajos pendientes abandonar ni cómo recuperar una operación parcialmente completada. Esas elecciones son parte de la solución propuesta para este caso.

Async no convierte un cálculo en trabajo de otro hilo

Hecho oficial: la guía de Node.js sobre bloqueo del event loop explica que los callbacks JavaScript se ejecutan en el event loop y que el trabajo prolongado puede retrasar a otros clientes. Declarar una función async no traslada su bucle síncrono a otro hilo.

En la comprobación de CPU registramos un setImmediate, llamamos a una función async y sumamos cien mil enteros dentro de ella. La traza muestra cpu_start, cpu_end y después immediate. La suma se realizó antes de devolver el control al llamador; el resultado fue 4.999.950.000.

Esta prueba muestra una secuencia de ejecución, no un benchmark de latencia ni una comparación entre máquinas. No hemos ejecutado un pool de Worker threads. Si la entrevista añade una transformación costosa, identifica primero el tramo síncrono y el tamaño de entrada que lo vuelve problemático.

Limitar las peticiones HTTP a dos no traslada esa transformación fuera del hilo principal. Podrías mantener pocas operaciones de red y seguir bloqueando el proceso con cada respuesta. A la inversa, unas consultas que esperan I/O pueden dejar libre el hilo y aun así agotar la capacidad del servicio dependiente.

La explicación necesita ambas observaciones: tiempo o trabajo del callback y número de operaciones pendientes. Una no sustituye a la otra. Elegir una técnica de partición u offload requeriría implementarla y volver a medir el escenario relevante.

El límite debe actuar antes de iniciar cada tarea

El patrón Promise.all(items.map(worker)) ya ha llamado a worker para todas las entradas cuando Promise.all recibe el array. Hecho documentado: Promise.all agrega resultados y puede rechazar al fallar una promesa; no establece un presupuesto de concurrencia.

Resultado del ejercicio: cuatro funciones se iniciaron antes de agregar sus promesas y el máximo observado fue cuatro. Pasar después esas mismas promesas a una utilidad «limitada» no deshace el inicio: las cuatro operaciones ya pueden estar consumiendo capacidad de la dependencia. Hay que conservar entradas o funciones que todavía no se hayan invocado.

Nuestra función mapBounded crea hasta dos consumidores. Cada uno toma un índice, espera su trabajo y solo entonces solicita el siguiente. Este es el núcleo del bucle; el archivo descargable incluye validación, eventos de diagnóstico y conservación del primer error:

while (!stopped && next < items.length) {
  const index = next++;
  try {
    results[index] = await worker(items[index], index);
  } catch (error) {
    if (!stopped) {
      stopped = true;
      firstError = error;
    }
  }
}

El contador de entrada reserva un índice antes del await; el resultado se escribe en su posición original. Los dos consumidores comparten next, stopped y firstError dentro de una invocación de mapBounded; cada nueva invocación crea otras variables. El ejemplo no es un algoritmo distribuido ni usa memoria compartida entre Workers.

También validamos que el límite sea un entero positivo, que la entrada sea un array y que el trabajador sea una función. Una entrada vacía devuelve un array vacío sin ejecutar tareas. Si el trabajador lanza un error sincrónico, el mismo bloque lo recoge y detiene la admisión posterior.

Conserva el orden aunque termine primero otra petición

Con límite dos se inician A y B. El ensayo libera B antes que A; su consumidor admite C. Después libera C y se admite D. D también termina antes que A. El orden de finalización observado es B, C, D, A.

La respuesta HTTP de éxito, sin embargo, conserva A, B, C, D porque cada salida utiliza el índice de entrada. No hace falta esperar a que termine A para aprovechar la plaza que dejó B. Tampoco se necesita ordenar después las salidas por una hora de finalización.

En este recorrido el servidor dependiente observa como máximo dos tareas activas. La traza registra B terminada antes de admitir C, C terminada antes de admitir D y A todavía activa después de terminar D. Esos eventos permiten comprobar dónde se repone una plaza. El paquete incluye trace.csv y las comparaciones literales de results.json.

Esta política tiene un coste: conservar el array completo requiere memoria proporcional al número de resultados. Nuestro endpoint admite solo cuatro identificadores y no implementa streaming. Para una importación grande, habría que diseñar lectura incremental, límites de cola y salida parcial, además de decidir si el orden estricto merece retener resultados ya disponibles.

Detener nuevas tareas no cancela las que ya empezaron

Ahora B devuelve HTTP 503 mientras A continúa esperando. El trabajador convierte ese estado HTTP en un rechazo. El planificador observa el fallo, guarda el primer error y cierra la admisión: C y D no llegan al servidor dependiente.

El contrato de este ejemplo espera a que los consumidores activos terminen antes de rechazar la operación completa. A acaba correctamente, incrementa su contador a uno y solo después el endpoint del lote devuelve HTTP 502 con UPSTREAM_503. Ese código y ese cuerpo son una decisión de nuestra API sintética, no una respuesta que Node imponga automáticamente.

Momento controladoQué sigue activo o confirmadoQué permite concluir
Comienzan A y BDos tareas en vuelo; C y D pendientesEl límite inicial es dos
Se observa el fallo de BA sigue activa; C y D no se admitenSe detiene el trabajo futuro
Termina ASu contador vale uno; el lote responde 502El error del lote no deshace su efecto
Se reintenta el lote completoA se vuelve a ejecutar y su contador llega a dosFalta una política que evite repetir ese efecto

Tras el fallo de B se detiene la admisión, A termina y el lote devuelve un error

No llames a esta implementación «rechazo inmediato»: drena el trabajo iniciado. Si A no terminara, tampoco habría respuesta por esta vía; falta un plazo de operación. Los cinco segundos del ayudante de pruebas detectan un ensayo bloqueado, pero no constituyen un timeout del endpoint.

Hecho oficial: la documentación de errores de Node.js distingue excepciones síncronas, rechazos de promesas, callbacks y eventos de error. Nuestro trabajador usa await dentro de try/catch; no suponemos que un único bloque externo capture cualquier error futuro de cualquier API.

Un aborto del cliente tampoco demuestra rollback

Hecho oficial: AbortController en Node.js comunica una señal a las APIs que la admiten. Para saber qué se detuvo, debes observar al consumidor de esa señal y al trabajo que ya recibió el otro extremo.

En una prueba separada enviamos una petición con fetch, esperamos a que el servidor la acepte y abortamos desde el cliente. Su promesa rechaza con AbortError. El servidor observa el cierre de la conexión, pero su trabajo sintético sigue activo, porque el manejador no utiliza ese cierre para detenerlo.

Liberamos entonces la compuerta del servidor y su contador se incrementa a uno. La conclusión está acotada: esta petición fetch abortada cerró su conexión, pero el manejador local continuó y modificó un contador en memoria. No se probó una escritura duradera ni un rollback remoto. No afirmamos que todos los servicios ignoren la desconexión ni que el aborto garantice revertir efectos remotos.

Antes de recomendar «reinténtalo», comprueba si A ya produjo el efecto que la persona esperaba una sola vez. En el ensayo anterior, repetir todo el lote repite A aunque su primera ejecución hubiera terminado bien. Una política idempotente, una consulta de estado o un registro de progreso podrían cambiar ese resultado, pero el laboratorio no las implementa. Expón qué propiedad necesita el negocio antes de presentar una repetición automática como recuperación segura.

Dos plazas por lote no son dos plazas para todo el servicio

La función crea su contador y sus consumidores por invocación. Dos llamadas simultáneas al endpoint mantienen dos presupuestos independientes. Resultado observado: cada lote respeta su máximo de dos y, aun así, los dos juntos sostienen cuatro operaciones activas en el servidor dependiente.

Este contraejemplo distingue el alcance del límite. Si la dependencia solo admite dos operaciones para todo el proceso, hay que coordinar un presupuesto compartido. Si existen varias réplicas, un contador local tampoco demostraría un límite global entre ellas. Ninguna de esas dos soluciones está implementada aquí.

Concurrencia y tasa tampoco son intercambiables. Dos plazas limitan trabajo simultáneo, no solicitudes por segundo. Un servicio que contesta rápido puede recibir muchos inicios sucesivos con pocas plazas. El ejercicio no incluye un reloj de admisión ni un token bucket.

En una conversación de diseño, pide el recurso protegido y la unidad del contrato: por lote, proceso, cuenta o conjunto de réplicas; operaciones simultáneas o inicios durante un intervalo. Después señala la prueba que faltaría. Así evitas responder «límite dos» a requisitos que solo coinciden en el número.

Practica el recorrido completo y defiende sus límites

Puedes descargar el laboratorio Node.js y ejecutar node lab.mjs desde la carpeta del paquete. No requiere dependencias npm. Arranca los dos servidores temporales, controla sus respuestas, guarda resultados y cierra las conexiones al terminar. El README explica qué archivos inspeccionar.

Para ensayar en voz alta, parte del éxito desordenado y cambia una condición: B falla antes de que termine A. Describe qué tareas se iniciaron, cuáles quedaron fuera y qué efecto persiste. Después añade una segunda petición del lote y vuelve a calcular el máximo conjunto.

Estas cinco preguntas publicadas permiten extender el mismo razonamiento. Sus contextos y títulos no convierten el laboratorio en un examen de esas empresas ni garantizan que aparecerán en tu entrevista.

Pregunta completaQué decisión practicar
Build and Review a Configurable Node.js Token-Bucket Rate LimiterDistingue tasa temporal de tareas simultáneas
Run Promise Tasks with Bounded ConcurrencyAdmite tareas sin iniciarlas todas antes del límite
Build an API aggregator with concurrency and retriesExplica qué efecto puede repetirse tras un error
Design an async job system and cache layerDefine propiedad de trabajos, progreso y recuperación
Lowest-Price Book Aggregator Fanning Out to Hundreds of Async BookstoresSepara presupuesto de fan-out, plazo y resultados parciales

Empieza por Run Promise Tasks with Bounded Concurrency. Antes de codificar, anuncia el orden de salida, el alcance del límite y la política tras un rechazo. Al terminar, usa la traza para comprobar esas tres promesas y nombra la primera extensión que exigiría una prueba distinta.

Sources and Further Reading


Comments (0)