Ir al contenido

El medio de los artesanos de la web miércoles, 16 de septiembre de 2026

MailStudio
Front-end

Debounce y throttle en JavaScript: controlar la frecuencia de eventos

Escritura, desplazamiento, redimensionado: algunos eventos se disparan decenas de veces por segundo y saturan el hilo principal. El debounce y el throttle regulan esa frecuencia, cada uno para una necesidad distinta que conviene diferenciar.

Debouncing et throttling en JavaScript

Un buscador que consulta al servidor en cada tecla, un manejador de scroll que recalcula una posición en cada píxel: estos patrones provocan cientos de llamadas inútiles y degradan la respuesta percibida. El debounce y el throttle son las dos respuestas clásicas. Se parecen, pero no resuelven el mismo problema, y confundirlos produce interfaces demasiado lentas o demasiado nerviosas.

Un mismo síntoma, dos estrategias

El punto común es la constatación: un evento se repite mucho más rápido que el trabajo que se le quiere asociar. La diferencia está en el momento elegido para actuar. El debounce espera una pausa antes de ejecutar el trabajo una sola vez. El throttle deja pasar una llamada a intervalos regulares e ignora las demás. El primero optimiza para «solo cuenta el último valor», el segundo para «basta con un ritmo controlado».

El debounce: actuar solo cuando vuelve la calma

Un debounce reinicia un temporizador en cada llamada y ejecuta la función solo si no llegó ninguna llamada nueva durante el retardo. Es el comportamiento esperado de un autocompletado: la petición parte solo cuando el usuario deja de escribir.

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

const search = debounce((query) => {
  fetch(`/api/search?q=${encodeURIComponent(query)}`);
}, 300);

input.addEventListener('input', (e) => search(e.target.value));

Ajustar el retardo es un compromiso: demasiado corto, la espera no sirve; demasiado largo, la interfaz parece inerte. Entre 200 y 400 milisegundos cubre la mayoría de los campos de entrada. Estas utilidades conviene tiparlas para que la función envuelta conserve su firma.

El throttle: garantizar un ritmo regular

Un throttle ejecuta la función de inmediato y luego bloquea cualquier llamada nueva durante una ventana dada. Sirve para flujos continuos donde importan los valores intermedios: desplazamiento, movimiento del ratón, redimensionado.

function throttle(fn, interval = 200) {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn.apply(this, args);
    }
  };
}

const onScroll = throttle(() => {
  updateScrollProgress(window.scrollY);
}, 200);

window.addEventListener('scroll', onScroll, { passive: true });

Para el trabajo puramente visual, requestAnimationFrame ofrece un throttle natural acompasado con la tasa de refresco de la pantalla, lo que evita recalcular más a menudo de lo que el navegador pinta.

CriterioDebounceThrottle
Momento de ejecuciónTras una pausaA intervalos regulares
Número de llamadasUna, al finalUna por ventana de tiempo
Valor que cuentaEl últimoTambién los intermedios
Caso típicoAutocompletado, validación de formulariosScroll, redimensionado, arrastre

El debounce espera el final; el throttle impone un tempo. Elegir uno por otro es atacar el problema equivocado.

Errores frecuentes y buenas prácticas

La función regulada debe crearse una sola vez, no en cada render: dentro de un componente, una instancia nueva en cada ciclo reinicia el temporizador y anula todo el efecto. También hay que retirar el escuchador al desmontar y, para un debounce, prever un método de cancelación cuando el contexto desaparece, por ejemplo al cerrar un campo.

Guía de elección. Si solo importa el valor final, basta un debounce. Si hace falta una respuesta continua pero acotada, un throttle. Y si el cálculo solo alimenta la visualización, requestAnimationFrame suele hacer innecesarios ambos.

Por último, conviene conocer la implementación antes de depender de una biblioteca. Las versiones de lodash gestionan opciones finas, como disparar al inicio y al final de la ventana, pero un puñado de líneas propias cubre la mayoría de los casos y evita una dependencia para un comportamiento que se domina.

Lo que conviene recordar

Debounce y throttle no se oponen, se complementan. El primero concentra una ráfaga de eventos en una acción final; el segundo suaviza un flujo continuo en un ritmo regular. El buen reflejo es nombrar primero la necesidad —último valor o cadencia acotada— y luego elegir la técnica, no al revés. Para el trabajo de solo visualización, la planificación por fotograma sigue siendo la herramienta más económica.

En mis proyectos, nueve de cada diez regulaciones se resuelven con una docena de líneas propias, sin dependencia. Solo recurro a la biblioteca cuando necesito el disparo al inicio y al final de la ventana a la vez, y entonces no tiene sentido reescribirlo. La verdadera trampa, la que más tiempo de depuración me ha costado, sigue siendo la función recreada en cada render. — Simon Janvier

Para profundizar: la referencia de MDN sobre requestAnimationFrame.

Compartir LinkedIn Bluesky Hacker News E-mail

Leer también