Preparación entrevista Node.js backend

Preguntas y respuestas de entrevista Node.js 2026

Una selección enfocada de preguntas Node.js para entrevistas backend, estructurada para practicar respuestas orales.

Empezar mock interview Node.js en español Práctica de entrevista técnica en inglésDiseñado para hablantes no nativos que desean practicar entrevistas técnicas en inglés.

Arquitectura y event loop

1¿Qué es Node.js y para qué se usa?

Node.js es un runtime de JavaScript fuera del navegador, construido sobre V8 y ampliado con APIs para red, archivos, procesos, streams y acceso al sistema operativo. Se usa habitualmente para REST APIs, servicios GraphQL, servidores WebSocket en tiempo real, microservicios, herramientas CLI, proxies y API gateways. Su punto fuerte es el I/O no bloqueante: un proceso puede manejar muchas conexiones de red de forma eficiente. El trade-off es que cálculos CPU-heavy largos pueden bloquear el event loop si no se mueven a workers, procesos u otro servicio.

2¿Cómo funciona Node.js internamente?

JavaScript se ejecuta en V8, mientras que las APIs de Node.js delegan trabajo asíncrono al sistema operativo o al thread pool de libuv. El hilo principal de JavaScript no espera a que termine una operación de I/O; continúa ejecutando otro código. Cuando la operación termina, su callback o continuación de Promise queda lista para el event loop. Así Node.js consigue alta concurrencia aunque el JavaScript de usuario normalmente se ejecute en un hilo principal.

3¿Qué son V8, libuv y el event loop?

V8 compila y ejecuta JavaScript y gestiona objetos y memoria. libuv proporciona el event loop cross-platform, integración con async I/O y un worker thread pool para operaciones como parte del filesystem, DNS y crypto. El event loop decide cuándo deben ejecutarse los callbacks de operaciones completadas. Node.js conecta las APIs JavaScript con capacidades nativas; V8 por sí solo no ofrece servidor HTTP, acceso a filesystem ni APIs de process.

4¿Por qué se dice que Node.js es single-threaded?

Un proceso Node.js normalmente ejecuta JavaScript de usuario en un hilo principal, así que dos request handlers normales no pueden ejecutar JavaScript exactamente al mismo tiempo en ese hilo. Pero la plataforma no es literalmente de un solo hilo: libuv tiene thread pool, el sistema operativo realiza async I/O, V8 hace trabajo de garbage collection y Node.js también soporta worker_threads y child processes. La frase describe sobre todo el modelo de ejecución JavaScript por defecto.

5¿Cómo puede Node.js manejar muchas peticiones concurrentes?

Node.js inicia operaciones de I/O y vuelve al event loop en vez de bloquear un hilo por petición. Por ejemplo, 1000 llamadas a base de datos pueden estar in flight mientras el hilo principal sigue aceptando conexiones y procesando callbacks listos. Funciona bien para workloads I/O-bound como HTTP calls, consultas a base de datos, archivos y sockets. Funciona mal cuando los request handlers hacen trabajo CPU síncrono pesado, porque eso bloquea el event loop y retrasa todas las demás peticiones.

6¿Cuáles son las fases principales del event loop de Node.js?

El event loop de libuv incluye fases como timers, pending callbacks, idle/prepare, poll, check y close callbacks. Timers ejecuta callbacks de setTimeout y setInterval; poll maneja muchos eventos de I/O; check ejecuta callbacks de setImmediate; close maneja eventos de cierre como sockets cerrándose. Node.js también tiene microtask queues, incluidas process.nextTick y Promise microtasks, que se ejecutan entre callbacks. Demasiadas microtasks pueden impedir que el I/O avance y hacer que el servicio parezca bloqueado.

7¿En qué se diferencian las operaciones síncronas, asíncronas y no bloqueantes?

Una operación síncrona devuelve antes de que se ejecute la siguiente línea y bloquea el hilo principal, como fs.readFileSync dentro de un request handler. Una operación asíncrona entrega su resultado más tarde mediante callback o Promise. No bloqueante significa que el hilo puede seguir haciendo otro trabajo mientras la operación está pendiente. Los términos están relacionados pero no son idénticos: async describe cómo llega el resultado, mientras non-blocking describe si el hilo espera. En backend request handlers normalmente se prefieren APIs async no bloqueantes.

import fs from 'node:fs';

const syncData = fs.readFileSync('file.txt', 'utf8');

const asyncData = await fs.promises.readFile(
  'file.txt',
  'utf8'
);

8¿Qué es event loop starvation?

Event loop starvation ocurre cuando trabajo JavaScript o microtasks mantienen ocupado el event loop y no le dejan procesar I/O, timers o nuevas peticiones con rapidez. Causas comunes son cálculos síncronos pesados, procesamiento enorme de JSON, cadenas de Promise sin límite y uso excesivo de process.nextTick. Las soluciones incluyen partir el trabajo en chunks, limitar tamaños de entrada, perfilar CPU, mover trabajo CPU-bound a worker_threads u otro servicio, y evitar APIs síncronas en rutas calientes.

app.get('/report', (req, res) => {
  const result = performVeryHeavyCalculation();
  res.json(result);
});

Programación async y errores

9¿Qué es un callback y qué es callback hell?

Un callback es una función que se pasa para ejecutarse más tarde, a menudo cuando termina una operación asíncrona. Los callbacks clásicos de Node.js suelen seguir el estilo error-first: callback(error, result). Callback hell aparece cuando operaciones dependientes quedan profundamente anidadas, haciendo difícil leer el control flow y manejar errores. La solución es descomponer en funciones con nombre, usar Promises o async/await, y ejecutar operaciones independientes con Promise.all en vez de anidarlas.

import fs from 'node:fs';

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log(data);
});

10¿Qué es una Promise y cómo funciona async/await?

Una Promise representa el resultado futuro de una operación asíncrona y puede estar pending, fulfilled o rejected. Una async function siempre devuelve una Promise. await pausa solo la async function actual hasta que la Promise se resuelve o rechaza; no bloquea todo el proceso Node.js. Una buena respuesta debe mencionar manejo de errores con try/catch, evitar Promises colgadas o no manejadas, y usar Promise.all para trabajo independiente para que las operaciones no se ejecuten innecesariamente en secuencia.

async function loadUser(id) {
  try {
    const user = await repository.findById(id);
    return user;
  } catch (error) {
    console.error(error);
    throw error;
  }
}

const [user, settings] = await Promise.all([
  getUser(),
  getSettings()
]);

11¿En qué se diferencian process.nextTick, queueMicrotask, setImmediate y setTimeout?

process.nextTick se ejecuta después de la operación actual y antes de que el event loop avance. queueMicrotask programa una microtask estándar de JavaScript, similar a continuaciones de Promise. setImmediate se ejecuta en la fase check. setTimeout(fn, 0) se ejecuta en la fase timers después de un retraso mínimo. Normalmente nextTick va antes que las Promise microtasks, y el orden entre setTimeout(0) y setImmediate depende del contexto; después de callbacks de I/O, setImmediate suele ejecutarse primero.

setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));

12¿Cómo se deben manejar errores en Node.js?

El código síncrono usa try/catch. El código basado en Promises debe usar await dentro de try/catch o adjuntar .catch. Los callbacks error-first deben comprobar el argumento error antes de usar el resultado. En aplicaciones de servidor son importantes middleware central de errores, logging con contexto de request, mensajes seguros para el cliente y graceful shutdown. uncaughtException no debe usarse para mantener corriendo un proceso corrupto; normalmente es una señal para registrar, limpiar y reiniciar.

try {
  JSON.parse(input);
} catch (error) {
  handleError(error);
}

try {
  await operation();
} catch (error) {
  handleError(error);
}

operation((error, result) => {
  if (error) return handleError(error);
  return result;
});

Módulos y gestión del proyecto

13¿Qué son los módulos en Node.js?

Un módulo es una unidad aislada de código con una interfaz exportada explícita. Node.js soporta módulos built-in como node:fs y node:http, módulos locales del proyecto, paquetes npm, CommonJS modules y ECMAScript modules. Los módulos hacen más claros los límites de responsabilidad y permiten reutilización. El trade-off en producción es el coupling: si los módulos se importan de forma demasiado profunda o esconden side effects al importarse, las pruebas y el startup se vuelven más difíciles de razonar.

// math.js
export function add(a, b) {
  return a + b;
}

// app.js
import { add } from './math.js';

14¿En qué se diferencian CommonJS require y ES Modules import?

CommonJS usa require y module.exports, mientras que ES Modules usa import y export. ES Modules soporta static analysis y top-level await, y se selecciona mediante package.json type: module o archivos .mjs. CommonJS suele cargarse de forma síncrona y puede marcarse con .cjs. En proyectos modernos de Node.js existen ambos sistemas, así que una respuesta sólida evita simplificar demasiado y explica que file extension, package.json y contexto del proyecto determinan el module mode.

// CommonJS
const fs = require('node:fs');
module.exports = { calculate };

// ES Modules
import fs from 'node:fs';
export { calculate };

15¿Qué son npm, package.json y package-lock.json?

npm es el package manager y CLI. package.json describe scripts, metadata, dependencies y devDependencies. package-lock.json registra el árbol exacto de dependencias para que las instalaciones sean reproducibles. En CI suele preferirse npm ci porque instala estrictamente desde el lock file y falla si package.json y el lock no coinciden. Esto protege los builds contra dependency drift accidental.

{
  "name": "api",
  "type": "module",
  "scripts": {
    "start": "node src/server.js",
    "test": "node --test"
  },
  "dependencies": {},
  "devDependencies": {}
}

16¿Cómo se deben gestionar configuración y variables de entorno?

Node.js lee variables de entorno mediante process.env, pero los valores son strings y deben validarse y convertirse en el startup. Los secrets no deben guardarse en el repositorio, .env debe ignorarse y los production secrets deben venir de la plataforma o de un secret manager. Los buenos servicios fallan rápido si falta configuración obligatoria. NODE_ENV es una convención, no una frontera de seguridad, así que el código no debe depender de él como mecanismo protegido.

const port = Number(process.env.PORT ?? 3000);
const databaseUrl = process.env.DATABASE_URL;

if (!databaseUrl) {
  throw new Error('DATABASE_URL is required');
}

Events, Buffer y streams

17¿Qué es EventEmitter?

EventEmitter implementa un patrón publish-subscribe dentro de un proceso Node.js. on registra un listener, once registra un listener para un solo evento, off elimina un listener y emit llama síncronamente a los listeners en orden de registro. La naturaleza síncrona es importante: un listener lento bloquea el emitter. El evento error es especial porque un error event no manejado puede cerrar el proceso. Los sistemas de larga vida también deben eliminar listeners no usados para evitar memory leaks.

import { EventEmitter } from 'node:events';

const emitter = new EventEmitter();

emitter.on('orderCreated', order => {
  console.log(`Order ${order.id} created`);
});

emitter.emit('orderCreated', { id: 42 });

18¿Para qué se usa Buffer?

Buffer representa una secuencia de bytes de tamaño fijo. Se usa para archivos, TCP sockets, HTTP bodies, imágenes, criptografía y protocolos binarios. Buffer.from crea un buffer desde datos existentes, Buffer.alloc crea memoria inicializada a cero, y Buffer.allocUnsafe es más rápido pero puede contener datos residuales de memoria hasta sobrescribirse. Por eso allocUnsafe solo es apropiado cuando el código rellena inmediatamente el buffer completo y nunca expone stale data.

const buffer = Buffer.from('Hello', 'utf8');

console.log(buffer);
console.log(buffer.toString('utf8'));

const safe = Buffer.alloc(1024);
const fast = Buffer.allocUnsafe(1024);

19¿Qué son los streams y qué tipos existen?

Los streams procesan datos por chunks en vez de cargar todo en memoria. Los tipos principales son Readable, Writable, Duplex y Transform. Archivos, objetos HTTP request y response, TCP sockets y gzip streams son ejemplos comunes. Los streams son valiosos para archivos grandes y tráfico de red porque reducen uso de memoria y latencia. Una respuesta de producción debe mencionar que los errores de stream deben manejarse correctamente en toda la cadena.

import fs from 'node:fs';

const input = fs.createReadStream('large.log');
const output = fs.createWriteStream('copy.log');

input.pipe(output);

20¿Qué es backpressure y por qué es útil pipeline?

Backpressure ocurre cuando una fuente produce chunks más rápido de lo que el destino puede consumirlos. Sin backpressure, la memoria puede crecer hasta que el proceso se ralentiza o cae. Streams y pipe pueden coordinar el flujo, pero pipeline de node:stream/promises es más seguro para cadenas de varios pasos porque propaga errores, cierra streams relacionados y devuelve una Promise. Es un buen default para compresión de archivos, uploads, downloads y transformaciones de streams.

import fs from 'node:fs';
import zlib from 'node:zlib';
import { pipeline } from 'node:stream/promises';

await pipeline(
  fs.createReadStream('input.txt'),
  zlib.createGzip(),
  fs.createWriteStream('input.txt.gz')
);