Ronda 1 · «ver si tienes experiencia con Node»

Node, sin fingir

La vacante oficial de Smartly en CDMX dice literalmente que su backend es Node.js y TypeScript. Eso confirma lo que te dijo Alex y te dice qué van a preguntar. Aquí están las doce preguntas probables con respuesta, y tu evidencia real.

CONTRASTELo que dijo Alex vs lo publicado

Alex dijoLa publicación confirma
"Trabajamos con Node"La vacante de Senior Backend Engineer en CDMX: "Our backend stack is built around Node.js and TypeScript"
"Mi equipo es Intelligence"El puesto habla de "data-intensive backend services", "data pipelines" y "platform integrations"
"80 % backend, 20 % frontend"Piden APIs, servicios distribuidos y operación en producción; el frontend no aparece como requisito
"Dos entrevistas más"El formato coincide con lo que se ve en su proceso: una técnica y una de diseño
—Nuevo: 250+ personas en Producto e Ingeniería, 750+ empleados, 24 oficinas en 13 países, híbrido 1 día/semana en CDMX

Fuente: vacante pública de Senior Backend Engineer, Smartly, Ciudad de México.

EVIDENCIATu experiencia real con Node

Esto es lo que puedes afirmar sin que se caiga en la segunda pregunta:

QuéNúmeros verificados
API de HeyDuck29.247 líneas · 20 controladores · 65 endpoints
Stack de esa APINestJS · Drizzle · PostgreSQL con RLS · BullMQ · Redis
conFirma636 archivos · 327 commits · Prisma · LangGraph
Proyectos de datos en Python≈ 9.700 líneas en cuatro repos públicos
Plataforma de su trabajo173.445 líneas de NestJS — coordinadas, no escritas (30 de 4.958 commits)

Cómo contestar «¿tienes experiencia con Node?»

"Yes — I built and operate a NestJS API: about twenty-nine thousand lines, sixty-five endpoints, PostgreSQL with row-level security, BullMQ queues on Redis, and Drizzle as the data layer. That's my own code, not a team's. At work I coordinate a much larger NestJS backend — a hundred and seventy thousand lines — but my contribution there is architecture and code review, not volume, and I say that plainly."

La última frase es la que te hace creíble. Sin ella, la primera pregunta de seguimiento te tumba.

TÉCNICALas doce preguntas

01Explícame el event loop
Respuesta

Node corre JavaScript en un solo hilo, y delega I/O al sistema (libuv). El event loop tiene fases: timers → callbacks pendientes → poll (I/O) → check (setImmediate) → close. Entre cada fase se vacían las microtareas (promesas), y process.nextTick corre antes que cualquier microtarea.

Consecuencia práctica: un Promise.resolve().then() se ejecuta antes que un setTimeout(0). Y setImmediate corre en la fase check, después del poll.

02¿Cómo se bloquea el event loop y cómo lo detectas?
Respuesta

Se bloquea con trabajo de CPU o I/O sincrónico: readFileSync, pbkdf2Sync, un JSON.parse de 200 MB, una expresión regular con retroceso catastrófico, un bucle de 10⁸ iteraciones.

Detección: event loop lag (medir con perf_hooks.monitorEventLoopDelay), latencia p99 que sube para todos los usuarios a la vez, no solo para quien llamó.

Arreglo: versión asíncrona, worker_threads para CPU, o mover el trabajo a otro servicio.

03Streams y contrapresión
Respuesta

Un stream tiene un búfer con highWaterMark. Si el consumidor es más lento que el productor, write() devuelve false: hay que esperar el evento drain. Si ignoras eso, el búfer crece hasta agotar memoria.

En la práctica: stream.pipeline() conecta y propaga errores y cierre correctamente. Es lo que usaría para leer un CSV de 2 GB o reenviar una respuesta sin cargarla en memoria.

04cluster, worker_threads o child_process
Respuesta

cluster: varios procesos que comparten el mismo puerto, para aprovechar núcleos en un servicio web. worker_threads: hilos dentro del mismo proceso con memoria compartida opcional, para CPU. child_process: programa externo, aislamiento total, más coste.

Hoy en la práctica: el escalado horizontal lo hace el orquestador (contenedores); worker_threads solo cuando hay CPU que no se puede sacar del proceso.

05Manejo de errores en asincronía
Respuesta

Un throw dentro de un callback no lo atrapa el try/catch de fuera. Con promesas hay que await o encadenar .catch(). unhandledRejection en producción es un fallo, no un aviso: se registra y se cierra el proceso con reinicio limpio, mejor que un estado indefinido.

Y para clientes HTTP: errores tipados, no strings. Un error de validación responde 422, uno de negocio 409, uno inesperado 500 sin filtrar el mensaje interno.

06Fugas de memoria en Node
Respuesta

Causas típicas: listeners que nunca se quitan (MaxListenersExceededWarning), cachés sin tope, closures que retienen objetos grandes, timers que nunca se limpian, acumular en un array global.

Detección: heap snapshot con dos tomas y comparar; process.memoryUsage().heapUsed creciendo en el tiempo; el RSS subiendo en el panel de métricas.

07Apagado ordenado
Respuesta

Al recibir SIGTERM: dejar de aceptar conexiones (server.close()), esperar las peticiones en vuelo, terminar los jobs en curso, cerrar el pool de base de datos y las colas, y salir. Con un timeout máximo para no quedarse colgado.

Y antes: un readiness que diga "no estoy listo" para que el balanceador deje de mandar tráfico.

08Colas: BullMQ y Redis
Respuesta

Semántica al menos una vez: un job se puede ejecutar dos veces, así que el consumidor tiene que ser idempotente. Concurrencia por worker, reintentos con backoff exponencial, y una cola de fallidos (dead letter) que alguien mira.

En HeyDuck usé seis tipos de job: correos, premios, agregados, webhooks. La métrica que importa no es el throughput: es la profundidad de la cola.

09Diseñar una API que aguante
Respuesta

Validación en la frontera con esquema (Zod) y tipos derivados, no duplicados. Claves de idempotencia en POST que crean dinero o recursos. Paginación por cursor, no offset. Un formato único de error. Límite de tasa por identidad. Y versionado o compatibilidad hacia atrás explícita.

10Postgres desde Node
Respuesta

Un pool, no una conexión por petición; y un tope de conexiones que case con el de la base (el clásico: 20 réplicas × 20 conexiones = 400 conexiones y Postgres ahogado). Transacciones para lo que toca varias tablas. Índices por lo que filtra de verdad. Y cuidado con N+1 al recorrer relaciones.

En HeyDuck añadí RLS: cada fila lleva organization_id y la política de Postgres filtra por el contexto de la sesión. El aislamiento no depende de que el código se acuerde.

11Pruebas en backend
Respuesta

Unitarias del dominio puro (sin base, sin red), de integración contra Postgres real (no mocks: los mocks mienten en SQL), y de contrato para la API. Reloj y azar inyectados para que las pruebas sean deterministas.

Y una regla: si una prueba tarda más de un segundo, la gente deja de correrla.

12Observabilidad
Respuesta

Logs estructurados con un request_id que atraviesa servicios; métricas de latencia p50 y p99 (no media), tasa de error, profundidad de cola y lag del event loop; trazas para seguir una petición entre servicios.

Y la pregunta que hay que poder contestar: "¿cómo sé que está roto antes que el usuario?"

CODINGQué esperar en la ronda de código

Con un puesto backend en Node, lo probable no es LeetCode difícil, sino lógica con JavaScript moderno y asincronía:

Practica en pruebas.ened.online: ya tiene promisePool, retry, debounce, once, curry, groupBy y memoize, con pruebas que se ejecutan de verdad.

Lo que no debes decir