Debounce e throttle: eventos sem travar a tela
Entenda a diferença entre debounce e throttle, quando usar cada um em busca com digitação, scroll e resize, e como implementar os dois em poucas linhas de JavaScript.
Alguns eventos do navegador disparam sem parar: digitar num campo, rolar a página, redimensionar a janela. Se cada disparo faz uma requisição ou um cálculo pesado, a tela trava e o servidor sofre. Debounce e throttle são as duas técnicas pra domar isso, e a confusão entre elas é muito comum.
O problema
Imagina uma busca que consulta a API a cada tecla:
input.addEventListener("input", (e) => {
buscar(e.target.value);
});Quem digita "notebook" dispara 8 requisições em um segundo, e só a última importa. As anteriores gastam rede, servidor e ainda podem chegar fora de ordem, mostrando o resultado de "noteb" depois do de "notebook".
Debounce: espere o usuário parar
Debounce adia a execução até que passe um tempo sem novos eventos. Cada evento novo reinicia a contagem. Só quando o silêncio dura o suficiente, a função roda, uma única vez.
function debounce(fn, atraso) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), atraso);
};
}
const buscarComDebounce = debounce(buscar, 400);
input.addEventListener("input", (e) => {
buscarComDebounce(e.target.value);
});Repare que isso só funciona por causa de uma closure: timer fica guardado entre as chamadas. Agora, digitando "notebook" de uma vez, só uma requisição sai, 400 ms depois da última tecla.
Use quando só o resultado final importa: campo de busca, validação de formulário enquanto digita, salvar rascunho automático.
Throttle: no máximo uma vez por intervalo
Throttle garante que a função rode no máximo uma vez a cada intervalo, não importa quantos eventos cheguem. Ele não espera parar, ele limita a cadência.
function throttle(fn, intervalo) {
let ultimaExecucao = 0;
return (...args) => {
const agora = Date.now();
if (agora - ultimaExecucao >= intervalo) {
ultimaExecucao = agora;
fn(...args);
}
};
}
window.addEventListener(
"scroll",
throttle(() => atualizarBarraDeProgresso(), 100)
);Durante uma rolagem contínua, a barra de progresso atualiza a cada 100 ms, em vez de centenas de vezes por segundo.
Use quando você precisa de atualizações contínuas, mas num ritmo controlado: scroll, resize, movimento do mouse, arrastar elementos.
A diferença, lado a lado
Pense num elevador com porta. Debounce é o porteiro que só fecha a porta quando ninguém mais entra há alguns segundos. Throttle é o elevador que sai a cada 10 minutos, tenha chegado muita gente ou pouca.
| Debounce | Throttle | |
|---|---|---|
| Quando executa | Depois que os eventos param | No máximo a cada intervalo |
| Durante a rajada | Nada acontece | Executa periodicamente |
| Ideal pra | Busca, validação | Scroll, resize, drag |
Em React
No React, o cuidado é não recriar a função debounced a cada renderização, senão o timer é perdido. Um hook resolve:
import { useEffect, useState } from "react";
function useDebounce(valor, atraso) {
const [debounced, setDebounced] = useState(valor);
useEffect(() => {
const timer = setTimeout(() => setDebounced(valor), atraso);
return () => clearTimeout(timer);
}, [valor, atraso]);
return debounced;
}
function Busca() {
const [texto, setTexto] = useState("");
const termo = useDebounce(texto, 400);
useEffect(() => {
if (termo) buscar(termo);
}, [termo]);
return <input value={texto} onChange={(e) => setTexto(e.target.value)} />;
}O campo responde na hora (o estado texto muda a cada tecla), mas a busca só reage a termo, que muda depois da pausa. O return () => clearTimeout(timer) limpa o timer anterior, que é exatamente o reinício do debounce.
Cuidados
- Escolha o atraso com critério. Debounce de 300 a 500 ms costuma ser bom pra busca. Muito alto parece lento, muito baixo não ajuda.
- Debounce não substitui cancelar requisições. Mesmo com ele, uma resposta antiga pode chegar depois da nova. Use
AbortControllerpra cancelar a anterior. - Em scroll, considere alternativas. Para detectar elementos entrando na tela,
IntersectionObserveré mais eficiente que ouvir scroll com throttle. - Bibliotecas existem. O lodash tem
debounceethrottleprontos, com opções extras como executar no início da rajada.
Fechando
Debounce espera o silêncio, throttle impõe um ritmo. Se só o resultado final interessa, debounce. Se o acompanhamento contínuo importa, throttle. Com uma closure e um setTimeout, você evita centenas de requisições desnecessárias e uma tela que engasga.
Direto na sua
caixa de entrada.
Um aviso por e-mail sempre que eu publicar um post novo. Sem spam, sem newsletter chata, só isso.
Leia também
- Paginação: offset vs cursor em listas grandes9 de novembro de 2026 · 4 min
- Closures em JavaScript: a função que lembra7 de novembro de 2026 · 4 min
- Jev com Hermes Agent: 4 usos para automatizar tarefas7 de novembro de 2026 · 6 min
- Regex sem medo: o básico que resolve 90% dos casos6 de novembro de 2026 · 4 min