Volver al inicio
Backend

Backend - Preguntas Comunes en Screening

Preguntas frecuentes que aparecen en procesos de screening técnico para posiciones de backend developer

ScreeningPreguntas ComunesEntrevistas

Introducción

Estas son algunas de las preguntas más comunes que encontrarás en procesos de screening técnico para posiciones de backend. Es importante tener claros estos conceptos fundamentales.

setTimeout, setImmediate, process.nextTick y Promises/async–await

1 preguntas

1

¿Cuál es la diferencia entre setTimeout, setImmediate, process.nextTick y Promises/async–await?

Orden de ejecución: Sync → process.nextTick → Promises → setImmediate → setTimeout
Comparación:
MétodoPrioridadCuándo se ejecuta
process.nextTick
⭐⭐⭐⭐⭐ Más alta
ANTES de la siguiente fase del event loop
Promises/async-await
⭐⭐⭐⭐ Alta
Microtask queue (después de nextTick)
setImmediate
⭐⭐ Media
Fase 'check' del event loop (después de I/O)
setTimeout
⭐ Baja
Fase 'timers' (puede tener delay)
Ejemplo:
javascript
console.log('1: Sync');
setTimeout(() => console.log('2: setTimeout'), 0);
setImmediate(() => console.log('3: setImmediate'));
Promise.resolve().then(() => console.log('4: Promise'));
process.nextTick(() => console.log('5: nextTick'));
console.log('6: Sync');
// Salida: 1, 6, 5, 4, 3 (o 2), 2 (o 3)
Cuándo usar:
•process.nextTick: Garantizar ejecución antes del event loop (cuidado con starvation)
•setImmediate: Preferible para código después de I/O
•Promises: Operaciones asíncronas modernas
•setTimeout: Operaciones con delay específico

Event Loop y Concurrencia

2 preguntas

1

¿Cómo funciona el event loop?

Concepto clave: Node.js es single-threaded pero maneja operaciones asíncronas eficientemente.
Fases del Event Loop:
1.Timers: Ejecuta callbacks de setTimeout y setInterval
2.Pending Callbacks: Ejecuta callbacks diferidos de I/O
3.Idle, Prepare: Uso interno
4.Poll: Obtiene nuevos eventos I/O
5.Check: Ejecuta callbacks de setImmediate
6.Close Callbacks: Ejecuta callbacks de close (ej: socket.on('close'))
Flujo:
┌───────────────────────────┐
│   Timers                  │ ← setTimeout, setInterval
├───────────────────────────┤
│   Pending Callbacks       │ ← I/O callbacks diferidos
├───────────────────────────┤
│   Poll                    │ ← Fetch nuevos eventos I/O
├───────────────────────────┤
│   Check                   │ ← setImmediate callbacks
├───────────────────────────┤
│   Close Callbacks         │ ← socket.on('close')
└───────────────────────────┘
Microtasks (mayor prioridad):
•process.nextTick
•Promises/async-await
Características:
✓Single-threaded: Un solo hilo ejecuta JavaScript
✓Non-blocking: I/O asíncrono no bloquea
✓Event-driven: Responde a eventos
Ejemplo:
javascript
console.log('Start');
setTimeout(() => console.log('Timer'), 0);
Promise.resolve().then(() => console.log('Promise'));
console.log('End');
// Salida: Start, End, Promise, Timer
2

¿Qué pasa si bloqueas el thread principal?

Problema: Node.js es single-threaded, si bloqueas el thread principal, toda la aplicación se congela.
Qué bloquea el thread:
✗ Loops síncronos largos:
javascript
// ❌ BLOQUEA TODO
for (let i = 0; i < 1000000000; i++) {
  // Cálculo pesado
}
✗ Operaciones síncronas de I/O:
javascript
// ❌ BLOQUEA
const data = fs.readFileSync('large-file.txt');

// ✅ NO BLOQUEA
fs.readFile('large-file.txt', (err, data) => {});
✗ Cálculos CPU-intensivos:
javascript
// ❌ BLOQUEA
function fibonacci(n) {
  if (n < 2) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci(40); // Bloquea todo
Consecuencias:
•No puede procesar nuevas requests
•Timers no se ejecutan
•Event loop se congela
•Usuarios experimentan timeout
•Aplicación parece "muerta"
Soluciones:
✓Dividir trabajo:
javascript
// Dividir en chunks
function processChunk(chunk, callback) {
  setImmediate(() => {
    // Procesar chunk
    callback();
  });
}
✓Worker Threads:
javascript
const { Worker } = require('worker_threads');
const worker = new Worker('./cpu-intensive-task.js');
worker.postMessage(data);
✓Child Processes:
javascript
const { fork } = require('child_process');
const child = fork('./heavy-computation.js');
child.send(data);
✓Usar async siempre que sea posible:
javascript
// ✅ NO BLOQUEA
await fs.promises.readFile('file.txt');
Regla: Mantén el event loop libre, delega trabajo pesado a workers o procesos separados

Manejo de Errores y Debugging

2 preguntas

1

¿Cómo manejar errores async correctamente?

Problema común: Errores en código asíncrono pueden no ser capturados si no se manejan correctamente.
Métodos de manejo:
1. Promises con .catch():
javascript
// ✅ CORRECTO
promiseFunction()
  .then(result => {
    // éxito
  })
  .catch(error => {
    // manejar error
    console.error(error);
  });

// ❌ INCORRECTO - Error no capturado
promiseFunction()
  .then(result => {
    throw new Error('Oops');
  });
// Error se pierde!
2. async/await con try-catch:
javascript
// ✅ CORRECTO
async function handler() {
  try {
    const result = await asyncFunction();
    return result;
  } catch (error) {
    console.error('Error:', error);
    throw error; // Re-throw si es necesario
  }
}
3. Error handlers en callbacks:
javascript
// ✅ CORRECTO
fs.readFile('file.txt', (err, data) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  // procesar data
});
4. process.on('unhandledRejection'):
javascript
// Capturar promesas rechazadas no manejadas
process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection:', reason);
  // Log, notificar, o cerrar proceso
});
Mejores prácticas:
✓Siempre manejar errores:
• Cada async operation debe tener error handling
• No dejar promesas sin .catch()
✓Error boundaries:
javascript
// Wrapper para manejar errores
async function safeAsync(fn) {
  try {
    return await fn();
  } catch (error) {
    console.error('Error in safeAsync:', error);
    return null;
  }
}
✓Propagar errores correctamente:
javascript
// Re-throw para que el caller maneje
catch (error) {
  logger.error(error);
  throw error; // Propagar
}
✓Error handling en Express:
javascript
app.use(async (req, res, next) => {
  try {
    await next();
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});
Errores comunes:
✗ Olvidar .catch() en promesas
✗ No usar try-catch con async/await
✗ Swallow errors (capturar y no hacer nada)
✗ No propagar errores cuando es necesario
2

¿Qué es un memory leak en Node y cómo lo detectas?

Definición: Memoria que ya no se usa pero no se libera, causando aumento gradual del uso de memoria.
Causas comunes:
✓Closures:
javascript
// ❌ LEAK: Referencia a objeto grande en closure
function leak() {
  const bigData = new Array(1000000).fill('data');
  return function() {
    console.log('Still holding reference to bigData');
  };
}
✓Event Listeners no removidos:
javascript
// ❌ LEAK
const emitter = new EventEmitter();
emitter.on('event', handler);
// Si no se remueve, el handler mantiene referencias

// ✅ CORRECTO
emitter.removeListener('event', handler);
// O usar once() para auto-remover
✓Timers no limpiados:
javascript
// ❌ LEAK
const interval = setInterval(() => {}, 1000);
// Si no se limpia, sigue ejecutándose

// ✅ CORRECTO
clearInterval(interval);
✓Referencias globales:
javascript
// ❌ LEAK
global.data = largeObject;

// ✅ CORRECTO
// Evitar variables globales o limpiarlas
Cómo detectar:
✓Node.js Inspector:
bash
node --inspect app.js
# Abrir chrome://inspect
✓heapdump:
javascript
const heapdump = require('heapdump');
heapdump.writeSnapshot('/tmp/heap-' + Date.now() + '.heapsnapshot');
✓clinic.js:
bash
npm install -g clinic
clinic doctor -- node app.js
✓Monitoreo:
javascript
// Ver uso de memoria
console.log(process.memoryUsage());
// { rss, heapTotal, heapUsed, external }
Señales de memory leak:
•Uso de memoria aumenta constantemente
•Performance degrada con el tiempo
•Crashes por "out of memory"
•GC frecuente pero memoria no baja

Streams y I/O

1 preguntas

1

¿Cuándo usar streams?

Definición: Streams permiten procesar datos en chunks en lugar de cargar todo en memoria.
Cuándo usar streams:
✓Archivos grandes:
javascript
// ❌ Carga todo en memoria
const data = fs.readFileSync('large-file.txt');

// ✅ Procesa en chunks
fs.createReadStream('large-file.txt')
  .pipe(transformStream)
  .pipe(fs.createWriteStream('output.txt'));
✓Datos de red:
javascript
// ✅ Stream HTTP response
app.get('/download', (req, res) => {
  const fileStream = fs.createReadStream('large-file.zip');
  fileStream.pipe(res);
});
✓Transformación de datos:
javascript
// ✅ Transformar mientras lees
const { Transform } = require('stream');

const upperCase = new Transform({
  transform(chunk, encoding, callback) {
    callback(null, chunk.toString().toUpperCase());
  }
});

readStream.pipe(upperCase).pipe(writeStream);
✓Procesamiento de logs:
javascript
// ✅ Procesar línea por línea
const readline = require('readline');
const rl = readline.createInterface({
  input: fs.createReadStream('access.log')
});

rl.on('line', (line) => {
  // Procesar cada línea
});
Tipos de streams:
TipoDescripciónEjemplo
Readable
Lee datos
fs.createReadStream
Writable
Escribe datos
fs.createWriteStream
Duplex
Lee y escribe
net.Socket
Transform
Transforma datos
crypto.createCipher
Ventajas:
✓Menor uso de memoria
✓Procesamiento más rápido
✓Puede empezar antes de tener todos los datos
✓Escalable para datos grandes
Cuándo NO usar:
✗ Datos pequeños (< 1MB)
✗ Necesitas acceso aleatorio
✗ Procesamiento simple y rápido
Ejemplo práctico:
javascript
// Procesar CSV grande
const csv = require('csv-parser');
fs.createReadStream('data.csv')
  .pipe(csv())
  .on('data', (row) => {
    // Procesar cada fila
  })
  .on('end', () => {
    console.log('Procesamiento completo');
  });

REST APIs

5 preguntas

1

¿Cómo versionas las REST APIs?

4 estrategias principales:
EstrategiaEjemplo✅ Ventajas❌ Desventajas
URL Path
/api/v1/users
Explícito, fácil de entender
URLs cambian con cada versión
Query Param
/api/users?version=1
URLs base no cambian
Menos RESTful, puede ignorarse
Header
Accept: application/vnd.api.v1+json
URLs limpias
Menos visible, requiere conocimiento
Media Type
Content-Type: ...;version=1
Separación de contenido
Similar a header
✅ Recomendación: URL Path Versioning (/api/v1/...)
Mejores prácticas:
•Mantener versiones anteriores durante transición
•Documentar cambios entre versiones
•Usar versionado semántico (v1, v2) no fechas
2

Explica las APIs idempotentes

Definición: Operación que produce el mismo resultado ejecutándola 1 vez o múltiples veces con los mismos parámetros.
Métodos HTTP:
Método¿Idempotente?Razón
GET
✅ Sí
Solo lee, no modifica estado
PUT
✅ Sí
Reemplaza completamente el recurso
DELETE
✅ Sí
Eliminar algo que no existe = mismo efecto
PATCH
❌ No
Cambios parciales pueden acumularse
POST
❌ No
Cada llamada puede crear nuevo recurso
Ejemplo:
PUT /api/users/123
{ "name": "Juan", "email": "juan@example.com" }
Ejecutar 1x o 10x → mismo resultado
¿Por qué importa?
✓Reintentos seguros sin efectos secundarios
✓Confiabilidad en redes inestables
✓Facilita retry logic
✓Evita duplicación
Mejores prácticas:
•PUT para actualizaciones completas
•PATCH solo para parciales
•Tokens de idempotencia para POST cuando sea necesario
3

¿Cómo diseñarías una API escalable?

Principios clave para escalabilidad:
1. Arquitectura:
✓Stateless: Sin estado en servidor, usar tokens (JWT)
✓Microservicios: Dividir en servicios independientes
✓Load Balancing: Distribuir carga entre múltiples servidores
✓Caching: Redis para datos frecuentes
✓CDN: Para assets estáticos
2. Base de datos:
✓Índices: Optimizar consultas frecuentes
✓Paginación: No retornar todos los datos
✓Read Replicas: Separar lecturas de escrituras
✓Sharding: Dividir datos por particiones
✓Connection Pooling: Reutilizar conexiones
3. Performance:
✓Rate Limiting: Prevenir abuso
✓Compresión: Gzip/Brotli para respuestas
✓Lazy Loading: Cargar datos bajo demanda
✓Batch Operations: Agrupar operaciones
✓Async Processing: Colas para tareas pesadas
4. Monitoreo y observabilidad:
✓Logging: Centralizado (ELK, Loki)
✓Metrics: Prometheus, Datadog
✓Tracing: Distributed tracing (Jaeger)
✓Health Checks: Endpoints de salud
✓Alerting: Notificaciones automáticas
5. Diseño de endpoints:
javascript
// ✅ Buen diseño
GET /api/v1/users?page=1&limit=20&sort=created_at
POST /api/v1/users
PUT /api/v1/users/:id
DELETE /api/v1/users/:id

// ❌ Mal diseño
GET /api/getAllUsers
POST /api/createUser
6. Versionado:
✓URL path versioning (/api/v1/...)
✓Mantener versiones anteriores
✓Documentar cambios
7. Seguridad:
✓HTTPS siempre
✓Autenticación robusta
✓Validación de inputs
✓Rate limiting
✓CORS configurado correctamente
Ejemplo de arquitectura escalable:
[Load Balancer]
    ↓
[API Servers] (stateless, múltiples instancias)
    ↓
[Cache Layer] (Redis)
    ↓
[Database] (Master + Read Replicas)
    ↓
[Message Queue] (RabbitMQ/Kafka para async)
4

¿Qué es HATEOAS?

Definición: Hypermedia As The Engine Of Application State - Principio REST que incluye links en las respuestas para navegar la API.
Concepto: El cliente descubre acciones disponibles a través de links en las respuestas, sin conocer URLs hardcodeadas.
Ejemplo sin HATEOAS:
json
// ❌ Cliente necesita conocer URLs
GET /api/users/123
{
  "id": 123,
  "name": "Juan"
}
// Cliente debe saber que existe PUT /api/users/123
Ejemplo con HATEOAS:
json
// ✅ Links incluidos en respuesta
GET /api/users/123
{
  "id": 123,
  "name": "Juan",
  "_links": {
    "self": { "href": "/api/users/123" },
    "update": { "href": "/api/users/123", "method": "PUT" },
    "delete": { "href": "/api/users/123", "method": "DELETE" },
    "orders": { "href": "/api/users/123/orders" }
  }
}
Ventajas:
✓Desacoplamiento: Cliente no necesita conocer URLs
✓Evolución: API puede cambiar URLs sin romper clientes
✓Descubrimiento: Cliente descubre capacidades dinámicamente
✓Navegación: Similar a navegar una página web
Desventajas:
✗ Más complejo de implementar
✗ Respuestas más grandes
✗ No siempre necesario para APIs simples
Cuándo usar:
✓APIs públicas complejas
✓Cuando URLs pueden cambiar
✓Cuando quieres que el cliente descubra capacidades
✓APIs que evolucionan frecuentemente
Cuándo NO usar:
✗ APIs internas simples
✗ Cuando el overhead no vale la pena
✗ APIs con contratos estables
Formato común:
json
{
  "data": {...},
  "_links": {
    "self": {"href": "/api/resource"},
    "next": {"href": "/api/resource?page=2"},
    "prev": {"href": "/api/resource?page=1"}
  }
}
5

REST vs GraphQL (cuándo usar cada uno)

Comparación rápida:
AspectoRESTGraphQL
Estructura
Múltiples endpoints
Un solo endpoint
Datos
Formato fijo
Cliente especifica qué necesita
Over-fetching
❌ Sí, puede traer datos innecesarios
✅ No, solo lo solicitado
Under-fetching
❌ Sí, puede necesitar múltiples requests
✅ No, todo en una query
Caching
✅ Fácil (HTTP caching)
⚠️ Más complejo
Complejidad
✅ Simple
❌ Más complejo
Tipado
❌ No
✅ Schema fuerte
Versionado
✅ Fácil (URLs)
⚠️ Schema evolution
REST - Usa cuando:
✓APIs simples: CRUD básico
✓Caching importante: Necesitas HTTP caching
✓Múltiples clientes: Diferentes necesidades
✓APIs públicas: Más fácil de entender
✓Microservicios: Cada servicio su propio endpoint
Ejemplo REST:
javascript
// Múltiples requests
GET /api/users/123
GET /api/users/123/posts
GET /api/users/123/comments
GraphQL - Usa cuando:
✓Cliente móvil: Necesita datos específicos (ahorro de ancho de banda)
✓Datos complejos: Múltiples relaciones
✓Over-fetching problemático: Datos grandes innecesarios
✓Frontend complejo: Necesita flexibilidad
✓Equipo con experiencia: GraphQL tiene curva de aprendizaje
Ejemplo GraphQL:
graphql
# Una sola query
query {
  user(id: 123) {
    name
    posts {
      title
      comments {
        text
      }
    }
  }
}
Híbrido:
✓Usar REST para operaciones simples
✓Usar GraphQL para queries complejas
✓GraphQL puede usar REST como fuente de datos
Mejores prácticas:
•REST: Para la mayoría de casos
•GraphQL: Cuando over-fetching es problema real
•Considerar complejidad del equipo
•Evaluar necesidades específicas del proyecto