Top 40 Preguntas de Entrevista de JavaScript en 2026 (Con Respuestas y Código)

Top 40 Preguntas de Entrevista de JavaScript en 2026 (Con Respuestas y Código)

Las preguntas de entrevista de JavaScript más frecuentes en 2026, con respuestas detalladas y ejemplos de código. Cubre desde fundamentos hasta conceptos avanzados de ES2025.

17 de marzo de 202620 min de lectura

Cómo Usar Esta Guía

Esta guía cubre las 40 preguntas más frecuentes en entrevistas técnicas de JavaScript para posiciones junior, mid-level y senior en 2026. Las preguntas están organizadas por dificultad: fundamentos, nivel intermedio y avanzado.

Para cada pregunta encontrarás:

  • La explicación conceptual
  • Ejemplos de código ejecutables
  • Lo que los entrevistadores realmente quieren escuchar
  • Errores comunes a evitar

Antes de la entrevista, practica validando tus expresiones regulares con el Testeador de Regex y verificando formatos de datos con el Formateador JSON.


Fundamentos (Preguntas 1-15)

1. ¿Cuál es la diferencia entre var, let y const?

Esta es la pregunta de entrada por excelencia. Muchos candidatos la responden superficialmente.

// var: función-scoped, hoisted, re-declarable
var x = 1;
var x = 2; // Sin error
console.log(x); // 2

function ejemplo() {
  var local = "dentro";
  if (true) {
    var tambiénLocal = "también accesible"; // var ignora bloques
  }
  console.log(tambiénLocal); // "también accesible"
}

// let: bloque-scoped, no re-declarable, en temporal dead zone
let y = 1;
// let y = 2; // SyntaxError
if (true) {
  let bloqueado = "solo aquí";
}
// console.log(bloqueado); // ReferenceError

// const: bloque-scoped, debe inicializarse, la referencia no cambia
const z = { nombre: "Juan" };
z.nombre = "María"; // OK: la referencia no cambia
// z = {}; // TypeError: la referencia SÍ cambia

// Truco que los entrevistadores esperan que sepas:
const ARRAY = [];
ARRAY.push(1); // OK
ARRAY.push(2); // OK
console.log(ARRAY); // [1, 2] - el array sí puede mutar

Lo que quieren escuchar: Mención del Temporal Dead Zone para let y const, y que const no hace el objeto inmutable, solo la referencia.


2. Explica el Hoisting en JavaScript

// ¿Qué imprime esto?
console.log(nombre); // undefined (no ReferenceError)
var nombre = "Juan";
console.log(nombre); // "Juan"

// Equivale a:
var nombre; // declaración hoisted al inicio
console.log(nombre); // undefined
nombre = "Juan"; // asignación en su lugar original
console.log(nombre); // "Juan"

// Con funciones: se hace hoisting completo (declaración + cuerpo)
saludar(); // "¡Hola!" - funciona antes de la declaración
function saludar() {
  console.log("¡Hola!");
}

// Con function expressions: solo la variable es hoisted
// despedirse(); // TypeError: despedirse is not a function
var despedirse = function() {
  console.log("¡Adiós!");
};

// let y const: hoisted pero no inicializados (Temporal Dead Zone)
// console.log(edad); // ReferenceError: Cannot access 'edad' before initialization
let edad = 25;

3. ¿Qué es el Closure y para qué sirve?

Un closure es una función que recuerda su entorno léxico incluso cuando se ejecuta fuera de él.

// Ejemplo clásico: contador privado
function crearContador() {
  let cuenta = 0; // Variable privada

  return {
    incrementar: () => ++cuenta,
    decrementar: () => --cuenta,
    obtener: () => cuenta,
    resetear: () => { cuenta = 0; }
  };
}

const contador = crearContador();
console.log(contador.incrementar()); // 1
console.log(contador.incrementar()); // 2
console.log(contador.obtener()); // 2
// No hay forma de acceder a 'cuenta' directamente desde fuera

// Uso práctico: memoización
function memoizar(fn) {
  const cache = new Map();

  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      console.log("Cache hit!");
      return cache.get(key);
    }
    const resultado = fn.apply(this, args);
    cache.set(key, resultado);
    return resultado;
  };
}

const fibonacci = memoizar(function fib(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(40)); // Rápido gracias al closure de cache

// Trampa clásica en entrevistas
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // Imprime: 3, 3, 3 (NO 0, 1, 2)
}

// Solución con closure
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 100); // Imprime: 0, 1, 2
  })(i);
}

// Solución moderna: usar let (bloque-scoped)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // Imprime: 0, 1, 2
}

4. ¿Cómo funciona this en JavaScript?

// 'this' depende del CONTEXTO DE EJECUCIÓN, no de dónde se define la función

// 1. En el contexto global
console.log(this); // window (browser) o {} (Node.js módulos)

// 2. En una función regular
function mostrar() {
  console.log(this);
}
mostrar(); // window/global (modo no estricto) o undefined (modo estricto)

// 3. En un método de objeto
const objeto = {
  nombre: "Mi Objeto",
  mostrar() {
    console.log(this.nombre); // "Mi Objeto"
  }
};

// 4. Pérdida de contexto (problema común)
const { mostrar: fn } = objeto;
fn(); // undefined - 'this' ya no es 'objeto'

// 5. Arrow functions: heredan 'this' del scope léxico
const objeto2 = {
  nombre: "Mi Objeto 2",
  metodoRegular: function() {
    const arrow = () => {
      console.log(this.nombre); // "Mi Objeto 2" - hereda de metodoRegular
    };
    arrow();
  }
};

// 6. bind, call, apply
function saludar(saludo, puntuacion) {
  return `${saludo}, ${this.nombre}${puntuacion}`;
}

const persona = { nombre: "Ana" };
saludar.call(persona, "Hola", "!");    // "Hola, Ana!"
saludar.apply(persona, ["Hola", "!"]); // "Hola, Ana!"
const saludarAna = saludar.bind(persona);
saludarAna("Hola", "!");               // "Hola, Ana!"

// 7. Con new
function Persona(nombre) {
  this.nombre = nombre; // 'this' es el nuevo objeto creado
}
const juan = new Persona("Juan");
console.log(juan.nombre); // "Juan"

5. Explica el Event Loop y la cola de microtareas

// El Event Loop ejecuta en este orden:
// 1. Call Stack (código sincrónico)
// 2. Microtask Queue (Promises, queueMicrotask)
// 3. Macrotask Queue (setTimeout, setInterval, I/O)

console.log("1: Sincrónico");

setTimeout(() => console.log("2: setTimeout"), 0);

Promise.resolve().then(() => console.log("3: Promise microtask"));

queueMicrotask(() => console.log("4: queueMicrotask"));

console.log("5: Sincrónico 2");

// Salida: 1, 5, 3, 4, 2
// Las microtareas siempre se ejecutan ANTES que las macrotareas

// Ejemplo práctico importante
async function ejemplo() {
  console.log("A: inicio async");
  await Promise.resolve();
  console.log("B: después de await"); // Microtask
}

ejemplo();
console.log("C: después de llamar ejemplo");

// Salida: A, C, B
// 'await' cede el control, 'C' se ejecuta, luego se resuelve la microtask 'B'

6. ¿Qué es el Prototype Chain?

// JavaScript usa herencia prototípica, no clásica
function Animal(nombre) {
  this.nombre = nombre;
}

Animal.prototype.hablar = function() {
  return `${this.nombre} hace un ruido.`;
};

function Perro(nombre) {
  Animal.call(this, nombre); // Llama al constructor padre
}

// Establecer la cadena de prototipos
Perro.prototype = Object.create(Animal.prototype);
Perro.prototype.constructor = Perro;

Perro.prototype.ladrar = function() {
  return `${this.nombre} ladra.`;
};

const rex = new Perro("Rex");
console.log(rex.ladrar());  // "Rex ladra."
console.log(rex.hablar());  // "Rex hace un ruido." (heredado de Animal)

// Verificar la cadena
console.log(rex instanceof Perro);  // true
console.log(rex instanceof Animal); // true
console.log(Object.getPrototypeOf(rex) === Perro.prototype); // true

// Versión moderna con clases (azúcar sintáctico del prototype)
class AnimalModerno {
  constructor(nombre) {
    this.nombre = nombre;
  }

  hablar() {
    return `${this.nombre} hace un ruido.`;
  }
}

class PerroModerno extends AnimalModerno {
  ladrar() {
    return `${this.nombre} ladra.`;
  }
}

7. ¿Cuál es la diferencia entre == y ===?

// == hace coerción de tipos
console.log(1 == "1");    // true - string "1" se convierte a número 1
console.log(0 == false);  // true - false se convierte a 0
console.log(null == undefined); // true - caso especial
console.log([] == false); // true - [] se convierte a "" y luego a 0

// === no hace coerción (comparación estricta)
console.log(1 === "1");   // false - tipos diferentes
console.log(0 === false); // false - tipos diferentes
console.log(null === undefined); // false

// Tabla de verdad que los entrevistadores conocen
console.log(null == 0);      // false - null solo es == a undefined
console.log(null == "");     // false
console.log(NaN == NaN);     // false - NaN nunca es igual a nada
console.log(NaN === NaN);    // false

// Para verificar NaN
console.log(Number.isNaN(NaN)); // true (preferido)
console.log(isNaN("hola"));     // true (coerciona antes de verificar)
console.log(Number.isNaN("hola")); // false (más preciso)

// Regla práctica: usa siempre ===, excepto para checar null/undefined
function esNuloOIndefinido(valor) {
  return valor == null; // Captura tanto null como undefined
}

8. ¿Qué son las Promesas y cómo funcionan?

// Crear una Promise
const promesa = new Promise((resolve, reject) => {
  const éxito = Math.random() > 0.5;

  setTimeout(() => {
    if (éxito) {
      resolve("¡Operación exitosa!");
    } else {
      reject(new Error("Operación fallida"));
    }
  }, 1000);
});

// Consumir
promesa
  .then(resultado => console.log(resultado))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Siempre se ejecuta"));

// Promise.all: todas deben resolverse
const p1 = fetch("/api/usuarios");
const p2 = fetch("/api/productos");
const p3 = fetch("/api/pedidos");

const [usuarios, productos, pedidos] = await Promise.all([p1, p2, p3]);

// Promise.allSettled: espera todas, sin importar si fallan
const resultados = await Promise.allSettled([p1, p2, p3]);
resultados.forEach(resultado => {
  if (resultado.status === "fulfilled") {
    console.log("Éxito:", resultado.value);
  } else {
    console.log("Error:", resultado.reason);
  }
});

// Promise.race: la primera que resuelve o rechaza
const conTimeout = (promise, ms) => Promise.race([
  promise,
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error("Timeout")), ms)
  )
]);

// Promise.any: la primera que RESUELVE (ignora rechazos)
const primeroExitoso = await Promise.any([p1, p2, p3]);

9. ¿Qué es async/await y cómo maneja errores?

// async/await es azúcar sintáctico sobre Promises
async function obtenerDatosUsuario(id) {
  try {
    const response = await fetch(`/api/usuarios/${id}`);

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const usuario = await response.json();
    return usuario;
  } catch (error) {
    console.error("Error al obtener usuario:", error);
    throw error; // Re-lanzar para que el llamador lo maneje
  }
}

// Manejo más sofisticado de errores
async function operacionRobusta() {
  // Helper para evitar try/catch repetitivos
  const [error, datos] = await atrapar(obtenerDatosUsuario(1));

  if (error) {
    // Manejar error específicamente
    return null;
  }

  return datos;
}

// Utility function estilo Go
async function atrapar(promise) {
  try {
    const datos = await promise;
    return [null, datos];
  } catch (error) {
    return [error, null];
  }
}

// Ejecución paralela con async/await
async function cargarDatos() {
  // MAL: secuencial cuando podría ser paralelo
  const usuarios = await fetch("/api/usuarios");
  const productos = await fetch("/api/productos"); // Espera a usuarios

  // BIEN: paralelo
  const [usuariosRes, productosRes] = await Promise.all([
    fetch("/api/usuarios"),
    fetch("/api/productos")
  ]);
}

10. Explica la Desestructuración en ES6+

// Desestructuración de objetos
const { nombre, edad, ciudad = "Madrid" } = usuario;

// Con renombrado
const { nombre: nombreUsuario, id: userId } = usuario;

// Anidada
const { direccion: { calle, codigoPostal } } = usuario;

// En parámetros de función
function mostrarUsuario({ nombre, edad, rol = "usuario" }) {
  return `${nombre} (${edad}) - ${rol}`;
}

// Desestructuración de arrays
const [primero, segundo, ...resto] = [1, 2, 3, 4, 5];
console.log(primero); // 1
console.log(resto);   // [3, 4, 5]

// Intercambio de variables
let a = 1, b = 2;
[a, b] = [b, a]; // Sin variable temporal
console.log(a, b); // 2, 1

// Desestructuración en iteración
const usuarios = [
  { id: 1, nombre: "Ana" },
  { id: 2, nombre: "Luis" }
];

for (const { id, nombre } of usuarios) {
  console.log(`${id}: ${nombre}`);
}

// Con Map
const mapa = new Map([["a", 1], ["b", 2]]);
for (const [clave, valor] of mapa) {
  console.log(`${clave} = ${valor}`);
}

Nivel Intermedio (Preguntas 11-25)

11. ¿Cómo funciona el Spread Operator y Rest Parameters?

// Spread: expande elementos
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const combinado = [...array1, ...array2]; // [1, 2, 3, 4, 5, 6]

// Clonar (shallow copy)
const original = { a: 1, b: { c: 2 } };
const copia = { ...original };
copia.a = 99; // No afecta a original
copia.b.c = 99; // SÍ afecta a original (shallow!)

// Spread en llamadas de función
const numeros = [1, 5, 3, 2, 4];
console.log(Math.max(...numeros)); // 5

// Rest parameters: agrupa argumentos en array
function sumar(...numeros) {
  return numeros.reduce((acc, n) => acc + n, 0);
}
console.log(sumar(1, 2, 3, 4, 5)); // 15

// Combinar en funciones
function primerYResto(primero, ...resto) {
  return { primero, resto };
}
console.log(primerYResto(1, 2, 3, 4)); // { primero: 1, resto: [2, 3, 4] }

12. ¿Qué son los Generadores?

function* contadorInfinito() {
  let i = 0;
  while (true) {
    yield i++;
  }
}

const contador = contadorInfinito();
console.log(contador.next().value); // 0
console.log(contador.next().value); // 1
console.log(contador.next().value); // 2

// Generador de IDs únicos
function* generadorId(prefijo = "ID") {
  let n = 1;
  while (true) {
    yield `${prefijo}-${String(n++).padStart(6, "0")}`;
  }
}

const ids = generadorId("USR");
console.log(ids.next().value); // "USR-000001"
console.log(ids.next().value); // "USR-000002"

// Iteración perezosa de grandes datasets
function* procesarLotes(datos, tamañoLote = 100) {
  for (let i = 0; i < datos.length; i += tamañoLote) {
    yield datos.slice(i, i + tamañoLote);
  }
}

for (const lote of procesarLotes(millonDeDatos, 500)) {
  procesarLote(lote); // Solo carga 500 elementos en memoria a la vez
}

13. Explica WeakMap y WeakSet

// WeakMap: claves deben ser objetos, referencias débiles (no evitan GC)
const cacheDatos = new WeakMap();

function obtenerDatosConCache(objeto) {
  if (cacheDatos.has(objeto)) {
    return cacheDatos.get(objeto);
  }

  const datos = calcularDatos(objeto);
  cacheDatos.set(objeto, datos);
  return datos;
}

// Cuando el objeto se elimina, la entrada en el WeakMap también
// WeakMap no es iterable (no hay forEach, keys, values, entries)

// Uso práctico: datos privados por instancia
const _privado = new WeakMap();

class CuentaBancaria {
  constructor(saldo) {
    _privado.set(this, { saldo, transacciones: [] });
  }

  depositar(cantidad) {
    const datos = _privado.get(this);
    datos.saldo += cantidad;
    datos.transacciones.push({ tipo: "depósito", cantidad });
  }

  get saldo() {
    return _privado.get(this).saldo;
  }
}

const cuenta = new CuentaBancaria(1000);
cuenta.depositar(500);
console.log(cuenta.saldo); // 1500
// No hay forma de acceder a datos privados desde fuera

14. ¿Cómo funciona la inmutabilidad en JavaScript?

// Object.freeze: inmutabilidad superficial
const config = Object.freeze({
  host: "localhost",
  puerto: 3000,
  opciones: { debug: true }
});

config.host = "remoto";       // Silencioso en modo no estricto
config.opciones.debug = false; // Funciona! freeze es shallow

// Freeze profundo
function freezeProfundo(obj) {
  Object.getOwnPropertyNames(obj).forEach(nombre => {
    const valor = obj[nombre];
    if (typeof valor === "object" && valor !== null) {
      freezeProfundo(valor);
    }
  });
  return Object.freeze(obj);
}

// Patrón de actualización inmutable
const estado = { usuario: { nombre: "Ana", edad: 28 }, tema: "oscuro" };

// MAL: mutación directa
estado.usuario.edad = 29;

// BIEN: crear nuevo objeto con cambios
const nuevoEstado = {
  ...estado,
  usuario: {
    ...estado.usuario,
    edad: 29
  }
};

// Actualización con structuredClone (clon profundo nativo en 2022+)
const estadoClon = structuredClone(estado);
estadoClon.usuario.edad = 29; // No afecta a estado original

15. Explica el manejo de errores avanzado

// Clases de errores personalizados
class ErrorValidacion extends Error {
  constructor(mensaje, campo, valor) {
    super(mensaje);
    this.name = "ErrorValidacion";
    this.campo = campo;
    this.valor = valor;
  }
}

class ErrorRed extends Error {
  constructor(mensaje, codigoHttp) {
    super(mensaje);
    this.name = "ErrorRed";
    this.codigoHttp = codigoHttp;
  }
}

// Manejo específico por tipo
async function procesarFormulario(datos) {
  try {
    validar(datos);
    await enviarAlServidor(datos);
  } catch (error) {
    if (error instanceof ErrorValidacion) {
      mostrarErrorCampo(error.campo, error.mensaje);
    } else if (error instanceof ErrorRed) {
      if (error.codigoHttp === 429) {
        await esperarYReintentar();
      } else {
        mostrarErrorGeneral("Error de conexión");
      }
    } else {
      // Error inesperado: log completo y reporte
      reportarError(error);
      throw error;
    }
  }
}

Preguntas Avanzadas (16-25)

16. ¿Qué es el Proxy en JavaScript?

// Proxy permite interceptar operaciones sobre objetos
const manejador = {
  get(objetivo, prop, receptor) {
    console.log(`Accediendo a: ${prop}`);
    return prop in objetivo ? objetivo[prop] : `Propiedad '${prop}' no existe`;
  },

  set(objetivo, prop, valor) {
    if (typeof valor !== "number") {
      throw new TypeError(`${prop} debe ser un número`);
    }
    objetivo[prop] = valor;
    return true;
  }
};

const datos = new Proxy({}, manejador);
datos.x = 10;        // OK
datos.nombre = "Ana"; // TypeError

// Caso de uso: validación automática de formularios
function crearModeloValidado(esquema) {
  return new Proxy({}, {
    set(obj, prop, valor) {
      if (esquema[prop]) {
        const { tipo, requerido, min, max } = esquema[prop];

        if (requerido && !valor) throw new Error(`${prop} es requerido`);
        if (tipo && typeof valor !== tipo) throw new TypeError(`${prop} debe ser ${tipo}`);
        if (min && valor < min) throw new RangeError(`${prop} debe ser >= ${min}`);
        if (max && valor > max) throw new RangeError(`${prop} debe ser <= ${max}`);
      }

      obj[prop] = valor;
      return true;
    }
  });
}

17. Patrones de Diseño en JavaScript Moderno

// Patrón Observer (Event Emitter personalizado)
class EventEmitter {
  #listeners = new Map();

  on(evento, callback) {
    if (!this.#listeners.has(evento)) {
      this.#listeners.set(evento, new Set());
    }
    this.#listeners.get(evento).add(callback);
    return () => this.off(evento, callback); // Retorna función para desuscribirse
  }

  off(evento, callback) {
    this.#listeners.get(evento)?.delete(callback);
  }

  emit(evento, ...args) {
    this.#listeners.get(evento)?.forEach(cb => cb(...args));
  }

  once(evento, callback) {
    const desuscribir = this.on(evento, (...args) => {
      callback(...args);
      desuscribir();
    });
  }
}

// Patrón Singleton
class ConfiguracionApp {
  static #instancia = null;
  #config = {};

  static obtenerInstancia() {
    if (!ConfiguracionApp.#instancia) {
      ConfiguracionApp.#instancia = new ConfiguracionApp();
    }
    return ConfiguracionApp.#instancia;
  }

  establecer(clave, valor) { this.#config[clave] = valor; }
  obtener(clave) { return this.#config[clave]; }
}

// Patrón Builder
class ConsultaSQL {
  #tabla = "";
  #condiciones = [];
  #columnas = ["*"];
  #limite = null;
  #orden = null;

  desde(tabla) { this.#tabla = tabla; return this; }
  seleccionar(...cols) { this.#columnas = cols; return this; }
  donde(condicion) { this.#condiciones.push(condicion); return this; }
  limitar(n) { this.#limite = n; return this; }
  ordenarPor(col, dir = "ASC") { this.#orden = `${col} ${dir}`; return this; }

  construir() {
    let sql = `SELECT ${this.#columnas.join(", ")} FROM ${this.#tabla}`;
    if (this.#condiciones.length) sql += ` WHERE ${this.#condiciones.join(" AND ")}`;
    if (this.#orden) sql += ` ORDER BY ${this.#orden}`;
    if (this.#limite) sql += ` LIMIT ${this.#limite}`;
    return sql;
  }
}

const consulta = new ConsultaSQL()
  .desde("usuarios")
  .seleccionar("id", "nombre", "email")
  .donde("activo = true")
  .donde("edad >= 18")
  .ordenarPor("nombre")
  .limitar(50)
  .construir();

// SELECT id, nombre, email FROM usuarios WHERE activo = true AND edad >= 18 ORDER BY nombre ASC LIMIT 50

18. Programación Funcional en JavaScript

// Funciones puras y composición
const añadirIVA = (porcentaje) => (precio) => precio * (1 + porcentaje / 100);
const aplicarDescuento = (descuento) => (precio) => precio - descuento;
const formatearPrecio = (decimales = 2) => (precio) => precio.toFixed(decimales);

// Composición de funciones (de derecha a izquierda)
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

const calcularPrecioFinal = pipe(
  añadirIVA(21),        // Añade 21% de IVA
  aplicarDescuento(5),  // Descuento de 5 euros
  formatearPrecio(2)    // Formatea a 2 decimales
);

console.log(calcularPrecioFinal(100)); // "116.00"

// Currying
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn(...args);
    }
    return (...moreArgs) => curried(...args, ...moreArgs);
  };
}

const multiplicar = curry((a, b, c) => a * b * c);
const duplicar = multiplicar(2)(1);
const triplicar = multiplicar(3)(1);

console.log(duplicar(5));  // 10
console.log(triplicar(5)); // 15

// Transducers para procesamiento eficiente de colecciones grandes
const mapTransducer = fn => reducer => (acc, val) => reducer(acc, fn(val));
const filterTransducer = pred => reducer => (acc, val) => pred(val) ? reducer(acc, val) : acc;

19. Web APIs Modernas: Intersection Observer, Resize Observer

// Intersection Observer: lazy loading eficiente
const observador = new IntersectionObserver(
  (entradas) => {
    entradas.forEach(entrada => {
      if (entrada.isIntersecting) {
        const img = entrada.target;
        img.src = img.dataset.src;
        img.classList.add("cargada");
        observador.unobserve(img); // Dejar de observar una vez cargada
      }
    });
  },
  {
    rootMargin: "200px 0px", // Cargar 200px antes de que sea visible
    threshold: 0.01
  }
);

document.querySelectorAll("img[data-src]").forEach(img => {
  observador.observe(img);
});

// Resize Observer: responder a cambios de tamaño
const resizeObs = new ResizeObserver(entradas => {
  for (const entrada of entradas) {
    const { width, height } = entrada.contentRect;
    console.log(`Nuevo tamaño: ${width}x${height}`);
    ajustarLayout(width);
  }
});

resizeObs.observe(document.getElementById("contenedor"));

// Performance Observer: medir rendimiento
const perfObs = new PerformanceObserver(lista => {
  lista.getEntries().forEach(entrada => {
    if (entrada.entryType === "largest-contentful-paint") {
      console.log(`LCP: ${entrada.startTime}ms`);
    }
  });
});

perfObs.observe({ type: "largest-contentful-paint", buffered: true });

20. TypeScript Integration: Tips Avanzados para Entrevistas 2026

// Tipos condicionales
type EsArray<T> = T extends Array<infer Item> ? Item : never;
type ElementoDeArray = EsArray<string[]>; // string
type NoEsArray = EsArray<string>; // never

// Template Literal Types
type EventosPagina = "inicio" | "productos" | "contacto";
type NombreEvento = `clic_${EventosPagina}`;
// "clic_inicio" | "clic_productos" | "clic_contacto"

// Utility Types avanzados
type SoloLectura<T> = { readonly [K in keyof T]: T[K] };
type Opcional<T> = { [K in keyof T]?: T[K] };
type SinNulos<T> = { [K in keyof T]: NonNullable<T[K]> };

// Inferencia de tipos en genéricos
function obtenerPropiedades<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  return keys.reduce((acc, key) => {
    acc[key] = obj[key];
    return acc;
  }, {} as Pick<T, K>);
}

// Discriminated Unions para manejo de estados
type EstadoPeticion<T> =
  | { estado: "inactivo" }
  | { estado: "cargando" }
  | { estado: "exitoso"; datos: T }
  | { estado: "error"; error: Error };

function renderizarEstado<T>(estado: EstadoPeticion<T>) {
  switch (estado.estado) {
    case "inactivo": return "Sin iniciar";
    case "cargando": return "Cargando...";
    case "exitoso": return `Datos: ${JSON.stringify(estado.datos)}`;
    case "error": return `Error: ${estado.error.message}`;
  }
}

Preguntas de Algoritmos y Lógica (Preguntas 21-30)

21. Implementa un debounce

function debounce(fn, espera) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), espera);
  };
}

// Con opción de ejecución inmediata
function debounceAvanzado(fn, espera, inmediato = false) {
  let timer;
  return function(...args) {
    const ejecutarAhora = inmediato && !timer;
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = null;
      if (!inmediato) fn.apply(this, args);
    }, espera);
    if (ejecutarAhora) fn.apply(this, args);
  };
}

// Uso práctico
const buscarConDebounce = debounce(async (termino) => {
  const resultados = await buscarEnAPI(termino);
  mostrarResultados(resultados);
}, 300);

inputBusqueda.addEventListener("input", e => buscarConDebounce(e.target.value));

22. Implementa un throttle

function throttle(fn, limite) {
  let enCooldown = false;
  return function(...args) {
    if (!enCooldown) {
      fn.apply(this, args);
      enCooldown = true;
      setTimeout(() => { enCooldown = false; }, limite);
    }
  };
}

// Throttle con cola (ejecuta el último mientras está en cooldown)
function throttleConCola(fn, limite) {
  let enCooldown = false;
  let llamadaPendiente = null;

  return function(...args) {
    if (enCooldown) {
      llamadaPendiente = args;
      return;
    }

    fn.apply(this, args);
    enCooldown = true;

    setTimeout(() => {
      enCooldown = false;
      if (llamadaPendiente) {
        fn.apply(this, llamadaPendiente);
        llamadaPendiente = null;
      }
    }, limite);
  };
}

// Uso: actualizar posición en scroll
const actualizarPosicion = throttle(() => {
  console.log("Scroll:", window.scrollY);
}, 100);

window.addEventListener("scroll", actualizarPosicion);

Preguntas Bonus de Lógica

// Pregunta clásica: aplanar array anidado sin flat()
function aplanar(arr) {
  return arr.reduce((acc, item) =>
    Array.isArray(item) ? [...acc, ...aplanar(item)] : [...acc, item]
  , []);
}

// Pregunta: implementar Promise.all desde cero
function promiseAllManual(promises) {
  return new Promise((resolve, reject) => {
    const resultados = [];
    let completadas = 0;

    if (promises.length === 0) {
      resolve([]);
      return;
    }

    promises.forEach((promise, i) => {
      Promise.resolve(promise)
        .then(valor => {
          resultados[i] = valor;
          completadas++;
          if (completadas === promises.length) {
            resolve(resultados);
          }
        })
        .catch(reject);
    });
  });
}

// Verificar palíndromo con regex (usa nuestro testeador)
const esPalindromo = str =>
  str.replace(/[^a-záéíóúüñ]/gi, "").toLowerCase() ===
  str.replace(/[^a-záéíóúüñ]/gi, "").toLowerCase().split("").reverse().join("");

console.log(esPalindromo("Anita lava la tina")); // true
console.log(esPalindromo("Amo la paloma"));       // true

Para probar los patrones de regex de tus respuestas, usa el Testeador de Regex. Para validar las respuestas JSON de tu código, el Formateador JSON es ideal.


Consejos para la Entrevista

Lo que los Entrevistadores Realmente Buscan

  1. Razonamiento en voz alta: No importa tanto llegar a la respuesta perfecta como mostrar tu proceso de pensamiento.

  2. Conocer las limitaciones: Saber cuándo una solución no es óptima y por qué es señal de madurez.

  3. Preguntas clarificadoras: Antes de programar, clarifica los requisitos. ¿El input siempre es válido? ¿Qué tamaño puede tener?

  4. Código limpio desde el inicio: Nombres descriptivos, funciones pequeñas, comentarios cuando sea necesario.

  5. Testing: Mencionar cómo testearías tu solución es un gran punto a favor.

Los 5 Errores Más Comunes

  1. No manejar casos de borde (arrays vacíos, null, undefined)
  2. Mutar datos de entrada sin advertencia
  3. No considerar la complejidad temporal y espacial
  4. Copiar sin entender código de Stack Overflow
  5. No hacer preguntas sobre los requisitos

Herramientas útiles para preparar tu entrevista: Formateador JSON, Testeador de Regex, Generador de Hash, Decodificador JWT.

Publicaciones relacionadas