Preguntas de entrevista Node.js Backend: Guia completa 2026

Las 25 preguntas mas frecuentes en entrevistas de backend Node.js. Event loop, async/await, streams, clustering y rendimiento explicados con respuestas detalladas.

Preguntas de entrevista Node.js Backend - Guia completa

Las entrevistas de backend Node.js evaluan la comprension de los mecanismos internos del runtime, el dominio de patrones asincronos y la capacidad de disenar aplicaciones de alto rendimiento. Esta guia cubre las preguntas mas frecuentes, desde los fundamentos hasta conceptos avanzados de produccion.

Consejo de entrevista

Los reclutadores valoran respuestas que combinan teoria con ejemplos practicos. Para cada pregunta, ilustrar con codigo o un caso de uso concreto demuestra experiencia real.

Fundamentos de Node.js

Pregunta 1: Que es el Event Loop y como funciona?

El Event Loop es el mecanismo central que permite a Node.js manejar operaciones asincronas de manera no bloqueante a pesar de ejecutarse en un solo hilo. Orquesta la ejecucion de codigo JavaScript, callbacks y eventos.

event-loop-demo.jsjavascript
// Demonstration of Event Loop execution order

console.log('1. Script start (synchronous)');

// setTimeout goes to the Timer Queue
setTimeout(() => {
  console.log('5. setTimeout callback (Timer Queue)');
}, 0);

// setImmediate goes to the Check Queue
setImmediate(() => {
  console.log('6. setImmediate callback (Check Queue)');
});

// Promise goes to the Microtask Queue (priority)
Promise.resolve().then(() => {
  console.log('3. Promise.then (Microtask Queue)');
});

// process.nextTick has the highest priority
process.nextTick(() => {
  console.log('2. process.nextTick (nextTick Queue)');
});

console.log('4. Script end (synchronous)');

// Output order: 1, 4, 2, 3, 5, 6

El Event Loop sigue un orden preciso al procesar las colas: primero el codigo sincrono, luego nextTick, microtasks (Promises), timers, callbacks de I/O, setImmediate y finalmente callbacks de cierre.

Pregunta 2: Cual es la diferencia entre process.nextTick() y setImmediate()?

Esta pregunta evalua la comprension detallada de las prioridades de ejecucion en el Event Loop.

nextTick-vs-immediate.jsjavascript
// Behavior comparison

// process.nextTick executes BEFORE the next Event Loop phase
process.nextTick(() => {
  console.log('nextTick 1');
  process.nextTick(() => {
    console.log('nextTick 2 (nested)');
  });
});

// setImmediate executes in the Check phase of the Event Loop
setImmediate(() => {
  console.log('setImmediate 1');
  setImmediate(() => {
    console.log('setImmediate 2 (nested)');
  });
});

// Output: nextTick 1, nextTick 2, setImmediate 1, setImmediate 2

process.nextTick() se procesa inmediatamente despues de la operacion actual, antes de que el Event Loop continue. El uso excesivo puede bloquear el Event Loop. setImmediate() es mas predecible y recomendado para diferir la ejecucion.

Cuidado con la inanicion

Las llamadas recursivas a process.nextTick() pueden agotar el Event Loop e impedir el procesamiento de I/O. Se recomienda setImmediate() para operaciones no criticas.

Pregunta 3: Como maneja Node.js los errores en codigo asincrono?

El manejo de errores asincronos difiere fundamentalmente del codigo sincrono. Sin un manejo adecuado, un error puede colapsar la aplicacion.

error-handling.jsjavascript
// Asynchronous error handling patterns

// Pattern 1: Callbacks with error-first convention
function readFileCallback(path, callback) {
  const fs = require('fs');
  fs.readFile(path, 'utf8', (err, data) => {
    if (err) {
      // Error is ALWAYS the first argument
      return callback(err, null);
    }
    callback(null, data);
  });
}

// Pattern 2: Promises with catch
async function readFilePromise(path) {
  const fs = require('fs').promises;
  try {
    const data = await fs.readFile(path, 'utf8');
    return data;
  } catch (err) {
    // Centralized error handling
    console.error(`File read error: ${err.message}`);
    throw err; // Re-throw for propagation
  }
}

// Pattern 3: Global handling of unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled rejection:', reason);
  // In production: log and graceful shutdown
});

// Pattern 4: Handling uncaught exceptions
process.on('uncaughtException', (err) => {
  console.error('Uncaught exception:', err);
  // CRITICAL: always terminate the process after
  process.exit(1);
});

En produccion, toda Promise debe tener un .catch() o estar dentro de un bloque try/catch. Los manejadores globales sirven como red de seguridad, no como la solucion principal.

Programacion asincrona y concurrencia

Pregunta 4: Explica la diferencia entre paralelismo y concurrencia en Node.js

Node.js es concurrente pero no paralelo por defecto. Esta distincion es fundamental para entender el rendimiento.

concurrency-vs-parallelism.jsjavascript
// CONCURRENCY: multiple tasks progress by alternating (single-thread)
async function concurrentTasks() {
  console.time('concurrent');

  // These calls are concurrent, not parallel
  const results = await Promise.all([
    fetch('https://api.example.com/users'),    // Non-blocking I/O
    fetch('https://api.example.com/products'), // Non-blocking I/O
    fetch('https://api.example.com/orders'),   // Non-blocking I/O
  ]);

  console.timeEnd('concurrent'); // ~time of the longest request
  return results;
}

// PARALLELISM: with Worker Threads for CPU-bound tasks
const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
  // Main thread delegates CPU-intensive work
  async function parallelComputation() {
    console.time('parallel');

    const workers = [
      createWorker({ start: 0, end: 1000000 }),
      createWorker({ start: 1000000, end: 2000000 }),
      createWorker({ start: 2000000, end: 3000000 }),
    ];

    const results = await Promise.all(workers);
    console.timeEnd('parallel');
    return results.reduce((a, b) => a + b, 0);
  }

  function createWorker(data) {
    return new Promise((resolve, reject) => {
      const worker = new Worker(__filename, { workerData: data });
      worker.on('message', resolve);
      worker.on('error', reject);
    });
  }
} else {
  // Code executed in the Worker Thread
  const { workerData } = require('worker_threads');
  let sum = 0;
  for (let i = workerData.start; i < workerData.end; i++) {
    sum += Math.sqrt(i); // CPU-intensive calculation
  }
  parentPort.postMessage(sum);
}

Para operaciones I/O-bound (red, archivos), la concurrencia nativa es suficiente. Para tareas CPU-bound (calculos pesados, criptografia), Worker Threads habilitan verdadero paralelismo.

Pregunta 5: Como funciona el modulo Cluster?

El modulo Cluster permite crear multiples procesos Node.js que comparten el mismo puerto, utilizando asi todos los nucleos de CPU disponibles.

cluster-example.jsjavascript
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);
  console.log(`Forking ${numCPUs} workers...`);

  // Fork one worker per CPU core
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  // Handle crashing workers
  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died (${signal || code})`);
    console.log('Starting a new worker...');
    cluster.fork(); // Automatic restart
  });

  // Inter-process communication
  cluster.on('message', (worker, message) => {
    console.log(`Message from worker ${worker.id}:`, message);
  });

} else {
  // Workers share the TCP port
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end(`Handled by worker ${process.pid}\n`);

    // Send stats to primary
    process.send({ type: 'request', pid: process.pid });
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
}

El balanceo de carga lo maneja automaticamente el sistema operativo (round-robin en Linux/macOS). En produccion, PM2 simplifica esta gestion con su modo cluster integrado.

¿Listo para aprobar tus entrevistas de Node.js / NestJS?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Streams y Buffers

Pregunta 6: Cuando usar Streams en lugar de metodos clasicos?

Los Streams permiten procesar datos por fragmentos en lugar de cargar todo en memoria. Esenciales para archivos grandes y escenarios de streaming.

streams-comparison.jsjavascript
const fs = require('fs');

// ❌ BAD: loads entire file into memory
async function readEntireFile(path) {
  const data = await fs.promises.readFile(path); // Blocks if file > RAM
  return processData(data);
}

// ✅ GOOD: chunk-based processing with Stream
function readWithStream(path) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    const readStream = fs.createReadStream(path, {
      highWaterMark: 64 * 1024, // 64KB per chunk
    });

    readStream.on('data', (chunk) => {
      // Progressive processing, constant memory
      chunks.push(processChunk(chunk));
    });

    readStream.on('end', () => resolve(chunks));
    readStream.on('error', reject);
  });
}

// ✅ BEST: pipeline for chaining transformations
const { pipeline } = require('stream/promises');
const zlib = require('zlib');

async function compressFile(input, output) {
  await pipeline(
    fs.createReadStream(input),   // Source
    zlib.createGzip(),            // Transform
    fs.createWriteStream(output)  // Destination
  );
  // Automatic error handling and backpressure management
}

Se recomienda usar Streams cuando el tamano de los datos pueda superar algunos MB, o para procesamiento en tiempo real (uploads, logs, datos de red).

Pregunta 7: Explica el concepto de backpressure

El backpressure ocurre cuando el productor de datos es mas rapido que el consumidor. Sin gestion, la memoria se desborda.

backpressure-demo.jsjavascript
const fs = require('fs');

// ❌ Problem: no backpressure handling
function badCopy(src, dest) {
  const readable = fs.createReadStream(src);
  const writable = fs.createWriteStream(dest);

  readable.on('data', (chunk) => {
    // If write() returns false, the internal buffer is full
    // But here reading continues anyway → memory leak
    writable.write(chunk);
  });
}

// ✅ Solution: respect the writable signal
function goodCopy(src, dest) {
  const readable = fs.createReadStream(src);
  const writable = fs.createWriteStream(dest);

  readable.on('data', (chunk) => {
    const canContinue = writable.write(chunk);

    if (!canContinue) {
      // Pause reading until buffer drains
      readable.pause();
    }
  });

  writable.on('drain', () => {
    // Buffer drained, resume reading
    readable.resume();
  });

  readable.on('end', () => writable.end());
}

// ✅ BEST: pipe() handles everything automatically
function bestCopy(src, dest) {
  const readable = fs.createReadStream(src);
  const writable = fs.createWriteStream(dest);

  // pipe() handles backpressure natively
  readable.pipe(writable);
}

El metodo pipe() o pipeline() gestiona el backpressure automaticamente. Para casos complejos, se implementa la logica de pause/resume manualmente.

Rendimiento y optimizacion

Pregunta 8: Como identificar y solucionar fugas de memoria?

Las fugas de memoria son comunes en Node.js. Saber detectarlas y corregirlas es esencial en produccion.

memory-leak-patterns.jsjavascript
// ❌ Leak 1: closures that retain references
function createLeakyHandler() {
  const hugeData = Buffer.alloc(100 * 1024 * 1024); // 100MB

  return function handler(req, res) {
    // hugeData remains in memory as long as handler exists
    res.end('Hello');
  };
}

// ✅ Fix: limit the scope
function createSafeHandler() {
  return function handler(req, res) {
    // Data created and released on each request
    const data = fetchData();
    res.end(data);
  };
}

// ❌ Leak 2: event listeners not cleaned up
class LeakyClass {
  constructor() {
    // Added on each instantiation, never removed
    process.on('message', this.handleMessage);
  }
  handleMessage(msg) { /* ... */ }
}

// ✅ Fix: explicit cleanup
class SafeClass {
  constructor() {
    this.boundHandler = this.handleMessage.bind(this);
    process.on('message', this.boundHandler);
  }
  handleMessage(msg) { /* ... */ }

  destroy() {
    // Mandatory cleanup
    process.removeListener('message', this.boundHandler);
  }
}

// Diagnostics with native tools
function diagnoseMemory() {
  const used = process.memoryUsage();
  console.log({
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,
    external: `${Math.round(used.external / 1024 / 1024)}MB`,
    rss: `${Math.round(used.rss / 1024 / 1024)}MB`,
  });
}

// Enable manual garbage collector for testing
// node --expose-gc app.js
if (global.gc) {
  global.gc();
  diagnoseMemory();
}

En produccion, se utilizan herramientas como clinic.js, heap snapshots de Chrome DevTools, o soluciones APM (Application Performance Monitoring) como DataDog o New Relic.

Pregunta 9: Como optimizar el rendimiento de una API Node.js?

Esta pregunta evalua el conocimiento de tecnicas de optimizacion en multiples niveles.

performance-optimization.jsjavascript
// 1. CACHING: reduce expensive calls
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300 }); // 5-minute TTL

async function getCachedUser(id) {
  const cacheKey = `user:${id}`;
  let user = cache.get(cacheKey);

  if (!user) {
    user = await db.users.findById(id);
    cache.set(cacheKey, user);
  }

  return user;
}

// 2. CONNECTION POOLING: reuse DB connections
const { Pool } = require('pg');
const pool = new Pool({
  max: 20,                // Max simultaneous connections
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

// 3. COMPRESSION: reduce response size
const compression = require('compression');
app.use(compression({
  filter: (req, res) => {
    // Only compress if > 1KB
    return compression.filter(req, res);
  },
  threshold: 1024,
}));

// 4. BATCHING: group operations
async function batchInsert(items) {
  const BATCH_SIZE = 1000;

  for (let i = 0; i < items.length; i += BATCH_SIZE) {
    const batch = items.slice(i, i + BATCH_SIZE);
    await db.items.insertMany(batch);
  }
}

// 5. LAZY LOADING: load on demand
async function getUserWithPosts(userId, includePosts = false) {
  const user = await db.users.findById(userId);

  if (includePosts) {
    user.posts = await db.posts.findByUserId(userId);
  }

  return user;
}

Las optimizaciones deben guiarse por el profiling. Medir antes de optimizar permite identificar los cuellos de botella reales.

La regla 80/20

El 80% de los problemas de rendimiento proviene del 20% del codigo. El profiling permite identificar estas areas criticas antes de optimizar a ciegas.

Seguridad

Pregunta 10: Como proteger una API Node.js contra ataques comunes?

La seguridad es un tema recurrente en entrevistas. Demostrar conocimiento de las vulnerabilidades OWASP es esperado.

security-best-practices.jsjavascript
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');

const app = express();

// 1. SECURITY HEADERS with Helmet
app.use(helmet());

// 2. RATE LIMITING against brute-force attacks
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,                  // 100 requests per IP
  message: 'Too many requests, please try again later',
  standardHeaders: true,
  legacyHeaders: false,
});
app.use('/api/', limiter);

// 3. SANITIZATION against NoSQL injections
app.use(mongoSanitize());

// 4. XSS PROTECTION
app.use(xss());

// 5. STRICT INPUT VALIDATION
const { body, validationResult } = require('express-validator');

app.post('/api/users',
  [
    body('email').isEmail().normalizeEmail(),
    body('password').isLength({ min: 8 }).escape(),
    body('name').trim().escape(),
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // Continue processing
  }
);

// 6. SQL INJECTION PROTECTION (with parameters)
async function safeQuery(userId) {
  // ✅ Parameterized query
  const result = await pool.query(
    'SELECT * FROM users WHERE id = $1',
    [userId]
  );
  return result.rows;
}

// ❌ NEVER string concatenation
async function unsafeQuery(userId) {
  // Vulnerable to SQL injection
  const result = await pool.query(
    `SELECT * FROM users WHERE id = ${userId}`
  );
}

En produccion, tambien se debe agregar: CORS restrictivo, HTTPS obligatorio, logging de seguridad, rotacion de secretos y auditorias regulares de dependencias (npm audit).

Arquitectura y patrones de diseno

Pregunta 11: Explica el patron Repository en Node.js

El patron Repository abstrae el acceso a datos y facilita las pruebas y la mantenibilidad.

repository-pattern.jsjavascript
// Abstract interface (for TypeScript, or documentation)
class UserRepository {
  async findById(id) { throw new Error('Not implemented'); }
  async findByEmail(email) { throw new Error('Not implemented'); }
  async create(userData) { throw new Error('Not implemented'); }
  async update(id, userData) { throw new Error('Not implemented'); }
  async delete(id) { throw new Error('Not implemented'); }
}

// Concrete implementation with Prisma
class PrismaUserRepository extends UserRepository {
  constructor(prisma) {
    super();
    this.prisma = prisma;
  }

  async findById(id) {
    return this.prisma.user.findUnique({ where: { id } });
  }

  async findByEmail(email) {
    return this.prisma.user.findUnique({ where: { email } });
  }

  async create(userData) {
    return this.prisma.user.create({ data: userData });
  }

  async update(id, userData) {
    return this.prisma.user.update({
      where: { id },
      data: userData,
    });
  }

  async delete(id) {
    return this.prisma.user.delete({ where: { id } });
  }
}

// Implementation for testing
class InMemoryUserRepository extends UserRepository {
  constructor() {
    super();
    this.users = new Map();
    this.idCounter = 1;
  }

  async findById(id) {
    return this.users.get(id) || null;
  }

  async create(userData) {
    const user = { id: this.idCounter++, ...userData };
    this.users.set(user.id, user);
    return user;
  }
  // ... other methods
}

// Service using the repository (dependency injection)
class UserService {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  async getUser(id) {
    const user = await this.userRepository.findById(id);
    if (!user) throw new Error('User not found');
    return user;
  }
}

Este patron permite cambiar la implementacion de persistencia sin modificar la logica de negocio.

Pregunta 12: Como implementar un sistema de colas de trabajo?

Las colas permiten diferir tareas pesadas y asegurar su ejecucion confiable.

job-queue.jsjavascript
const Queue = require('bull');

// Create queue with Redis as backend
const emailQueue = new Queue('email', {
  redis: {
    host: 'localhost',
    port: 6379,
  },
  defaultJobOptions: {
    attempts: 3,          // Number of attempts
    backoff: {
      type: 'exponential',
      delay: 2000,        // Initial delay between attempts
    },
    removeOnComplete: 100, // Keep last 100 completed jobs
  },
});

// Producer: add jobs to the queue
async function sendWelcomeEmail(userId, email) {
  await emailQueue.add('welcome', {
    userId,
    email,
    template: 'welcome',
  }, {
    priority: 1,          // High priority
    delay: 5000,          // 5-second delay
  });
}

// Consumer: process jobs
emailQueue.process('welcome', async (job) => {
  const { userId, email, template } = job.data;

  // Update progress
  job.progress(10);

  const html = await renderTemplate(template, { userId });
  job.progress(50);

  await sendEmail(email, 'Welcome!', html);
  job.progress(100);

  return { sent: true, email };
});

// Event handling
emailQueue.on('completed', (job, result) => {
  console.log(`Job ${job.id} completed:`, result);
});

emailQueue.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed:`, err.message);
});

// Recurring jobs (cron)
emailQueue.add('newsletter', { type: 'weekly' }, {
  repeat: {
    cron: '0 9 * * MON', // Every Monday at 9am
  },
});

Bull con Redis es la solucion mas popular. Para necesidades mas simples, agenda o bee-queue son alternativas ligeras.

Preguntas avanzadas

Pregunta 13: Como funciona el modulo nativo N-API?

N-API permite crear modulos nativos en C/C++ con una API estable entre versiones de Node.js.

native-module.cppcpp
// Native module for CPU-intensive calculations

#include <napi.h>

// Synchronous function exposed to JavaScript
Napi::Number Fibonacci(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();

  // Argument validation
  if (info.Length() < 1 || !info[0].IsNumber()) {
    Napi::TypeError::New(env, "Number expected")
      .ThrowAsJavaScriptException();
    return Napi::Number::New(env, 0);
  }

  int n = info[0].As<Napi::Number>().Int32Value();

  // Iterative Fibonacci calculation
  long long a = 0, b = 1;
  for (int i = 0; i < n; i++) {
    long long temp = a + b;
    a = b;
    b = temp;
  }

  return Napi::Number::New(env, static_cast<double>(a));
}

// Module initialization
Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set(
    Napi::String::New(env, "fibonacci"),
    Napi::Function::New(env, Fibonacci)
  );
  return exports;
}

NODE_API_MODULE(native_module, Init)
javascript
// Usage from JavaScript
const native = require('./build/Release/native_module');

// 10x faster than JavaScript equivalent
const result = native.fibonacci(50);

Los modulos nativos son utiles para calculos intensivos, integracion de bibliotecas C/C++ existentes o acceso a APIs del sistema.

Pregunta 14: Explica el Garbage Collector de V8

Comprender el GC permite escribir codigo que minimiza las pausas y el consumo de memoria.

gc-optimization.jsjavascript
// V8 GC uses two spaces: Young and Old Generation

// 1. Young Generation: short-lived objects
function shortLivedObjects() {
  for (let i = 0; i < 1000; i++) {
    const temp = { data: i }; // Allocated then collected quickly
  }
  // Minor GC (Scavenge) very fast
}

// 2. Old Generation: objects that survive multiple GCs
const cache = new Map(); // Survives, promoted to Old Generation

// ❌ Problematic pattern: many promoted objects
function createManyLongLived() {
  const objects = [];
  for (let i = 0; i < 100000; i++) {
    objects.push({ id: i, data: new Array(100).fill(0) });
  }
  return objects; // All promoted to Old Gen = slow major GC
}

// ✅ Optimized pattern: object reuse
class ObjectPool {
  constructor(factory, size = 100) {
    this.pool = Array.from({ length: size }, factory);
    this.available = [...this.pool];
  }

  acquire() {
    return this.available.pop() || this.pool[0];
  }

  release(obj) {
    // Reset and return to pool
    Object.keys(obj).forEach(k => obj[k] = null);
    this.available.push(obj);
  }
}

// GC monitoring
const v8 = require('v8');

function getHeapStats() {
  const stats = v8.getHeapStatistics();
  return {
    totalHeap: `${Math.round(stats.total_heap_size / 1024 / 1024)}MB`,
    usedHeap: `${Math.round(stats.used_heap_size / 1024 / 1024)}MB`,
    heapLimit: `${Math.round(stats.heap_size_limit / 1024 / 1024)}MB`,
  };
}

El flag --max-old-space-size permite aumentar el limite de Old Generation para aplicaciones con uso intensivo de memoria.

Pregunta 15: Como implementar un apagado graceful?

El apagado graceful permite completar las solicitudes en curso y cerrar correctamente las conexiones antes de detener el servidor.

graceful-shutdown.jsjavascript
const http = require('http');

const server = http.createServer((req, res) => {
  // Simulate a long request
  setTimeout(() => {
    res.writeHead(200);
    res.end('Done');
  }, 2000);
});

// Tracking active connections
let connections = new Set();

server.on('connection', (conn) => {
  connections.add(conn);
  conn.on('close', () => connections.delete(conn));
});

// Graceful shutdown function
async function shutdown(signal) {
  console.log(`${signal} received, starting graceful shutdown...`);

  // 1. Stop accepting new connections
  server.close(() => {
    console.log('HTTP server closed');
  });

  // 2. Close idle connections
  for (const conn of connections) {
    conn.end();
  }

  // 3. Close DB connections, queues, etc.
  await Promise.all([
    database.disconnect(),
    redisClient.quit(),
    messageQueue.close(),
  ]);

  // 4. Safety timeout
  setTimeout(() => {
    console.error('Forced shutdown after timeout');
    process.exit(1);
  }, 30000);

  console.log('Graceful shutdown completed');
  process.exit(0);
}

// Listen for termination signals
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));

// Start server
server.listen(3000, () => {
  console.log('Server running on port 3000');
});

En produccion con contenedores (Docker, Kubernetes), el apagado graceful es critico para despliegues sin tiempo de inactividad.

¿Listo para aprobar tus entrevistas de Node.js / NestJS?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Preguntas conductuales

Pregunta 16: Describe un problema de rendimiento que resolviste

Esta pregunta evalua la experiencia practica. Se recomienda estructurar la respuesta usando el formato STAR (Situacion, Tarea, Accion, Resultado).

Ejemplo de respuesta estructurada:

text
Situation: A reporting API was timing out on requests
            exceeding 100,000 records.

Task:       Reduce response time from 45s to under 5s.

Action:
1. Profiling with clinic.js → identified JSON serialization as bottleneck
2. Implemented streaming with Transform streams
3. Database-side pagination
4. Added Redis caching for frequent queries

Result:     Response time reduced to 2s, memory usage decreased by 10x.

Pregunta 17: Como gestionas las dependencias y sus actualizaciones?

package.json - Versioning best practicesjavascript
{
  "dependencies": {
    // ✅ Exact versions for production
    "express": "4.18.2",

    // ✅ Caret for compatible minor updates
    "lodash": "^4.17.21",

    // ❌ Avoid latest or *
    // "some-lib": "*"
  },
  "devDependencies": {
    // Quality tools
    "npm-check-updates": "^16.0.0"
  },
  "scripts": {
    // Vulnerability check
    "audit": "npm audit --audit-level=moderate",

    // Interactive update
    "update:check": "ncu",
    "update:apply": "ncu -u && npm install"
  },
  "engines": {
    // Specify required Node.js version
    "node": ">=20.0.0"
  }
}

Se recomienda mencionar el uso de package-lock.json, Dependabot o Renovate para automatizacion, y pruebas de regresion antes de cada actualizacion mayor.

Conclusion

Las entrevistas de backend Node.js evaluan tanto la comprension teorica de los mecanismos internos como la capacidad de resolver problemas practicos de produccion. Dominar el Event Loop, los patrones asincronos y las tecnicas de optimizacion forma la base esperada para puestos de desarrollador backend senior.

Checklist de preparacion

  • ✅ Entender el funcionamiento del Event Loop y sus fases
  • ✅ Dominar las diferencias entre callbacks, Promises y async/await
  • ✅ Conocer los patrones de manejo de errores asincronos
  • ✅ Saber cuando usar Streams vs metodos clasicos
  • ✅ Identificar y corregir fugas de memoria
  • ✅ Aplicar mejores practicas de seguridad OWASP
  • ✅ Implementar clustering y apagado graceful
  • ✅ Utilizar herramientas de profiling (clinic.js, Chrome DevTools)

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

La preparacion tecnica debe complementarse con proyectos practicos. Construir una API de produccion, contribuir a proyectos open source de Node.js o resolver desafios en plataformas como LeetCode ayuda a solidificar estos conocimientos.

Etiquetas

#nodejs
#interview
#backend
#javascript
#technical interview

Compartir

Artículos relacionados