setTimeout e setInterval em JavaScript
O atraso que você pede é o mínimo, nunca o exato. Como agendar, cancelar e medir timers em JavaScript sem vazar intervalo nem congelar a interface.
setTimeout agenda uma função para rodar uma vez, depois de um atraso.
setInterval agenda para rodar repetidamente, a cada intervalo. Os dois
devolvem um identificador que serve para cancelar — clearTimeout e
clearInterval.
O que quase nenhum tutorial diz na primeira linha: o número que você passa é o atraso mínimo, não o exato. O timer entra na fila; ele só roda quando a thread estiver livre. Tudo neste texto sai dessa frase.
Timer é despertador, não compromisso de agenda
Quando você programa um despertador para daqui a dez minutos, ele fica pronto para tocar naquele horário. Mas, se o alto-falante estiver ocupado, o som sai assim que houver espaço. O timer do JavaScript funciona com a mesma ideia: depois do atraso, a função entra na fila; quem decide quando ela realmente roda é a disponibilidade da thread.
O nome técnico do número é delay, o atraso mínimo em milissegundos. Ele não
é uma promessa de pontualidade. Para sentir a diferença, tente prever a ordem
de três mensagens: uma impressa agora, outra num setTimeout(..., 0) e uma
conta longa entre as duas. Depois execute. Esse pequeno teste vale mais que
decorar “zero milissegundos”, porque mostra que zero ainda significa “assim que
a fila puder”.
Agendar: função, atraso e argumentos extras
O primeiro argumento é a função — sem parênteses. O segundo é o atraso em milissegundos. Do terceiro em diante, os argumentos que você quer entregar para a função na hora da chamada.
console.log('pedido criado');
setTimeout(() => {
console.log('e-mail de confirmação enviado');
}, 1000);
setTimeout((cliente, valor) => {
console.log(`aviso de separação: ${cliente}, R$ ${valor.toFixed(2)}`);
}, 500, 'Ana', 289.9);
console.log('resposta devolvida para o navegador');As duas linhas síncronas saíram primeiro, e os agendamentos saíram na ordem do atraso — não na ordem em que foram escritos. O programa não parou em nenhum momento para esperar.
Cancelar antes de disparar
O retorno do setTimeout é o identificador. No navegador é um número; no Node é
um objeto Timeout:
const id = setTimeout(() => console.log('carrinho abandonado: cupom enviado'), 500);
console.log('id do timer:', id.constructor.name);
// o cliente finalizou a compra antes dos 500ms
clearTimeout(id);
console.log('compra finalizada — timer cancelado');O cupom nunca foi enviado. Esse padrão — agendar e cancelar se o usuário agir antes — é o coração do debounce de campo de busca.
O atraso é o mínimo, e dá para medir
Peço um timer de 100ms e coloco uma recontagem de estoque síncrona logo abaixo:
const inicio = Date.now();
setTimeout(() => {
console.log('timer de 100ms disparou aos', Date.now() - inicio, 'ms');
}, 100);
// recontagem síncrona do estoque
let peso = 0;
for (let i = 0; i < 900_000_000; i++) peso += i % 7;
console.log('recontagem terminou aos', Date.now() - inicio, 'ms');432ms de atraso sobre o que foi pedido, e ninguém errou nada: o timer ficou pronto aos 100ms e esperou a pilha desocupar. Esse é o mesmo mecanismo descrito em JavaScript assíncrono — vale a leitura se a fila e a pilha ainda parecerem abstratas.
O clamp de 4ms — e o que o Node faz de diferente
A especificação HTML manda o navegador travar o atraso mínimo em 4ms quando o
timer está aninhado a mais de cinco níveis (um setTimeout agendado dentro de
outro, e assim por diante). É por isso que uma animação feita com
setTimeout(f, 0) recursivo trava em torno de 250 quadros por segundo no
navegador.
O Node não aplica essa regra. Medindo oito níveis de aninhamento:
let nivel = 0;
let anterior = Date.now();
function proxima() {
const agora = Date.now();
console.log(`nivel ${nivel}: ${agora - anterior}ms desde o anterior`);
anterior = agora;
nivel += 1;
if (nivel < 8) setTimeout(proxima, 0);
}
setTimeout(proxima, 0);Nenhum salto para 4ms a partir do quinto nível: no Node o mínimo é 1ms e fica nisso. Ou seja, o mesmo código de agendamento roda com ritmos diferentes no servidor e no navegador — e a medição acima é a razão para não calibrar animação por contagem de disparos.
setInterval: o clearInterval que ninguém escreve
Um contador que se encerra sozinho precisa guardar o id e chamar
clearInterval dentro do próprio callback:
let restante = 3;
const id = setInterval(() => {
console.log(`carrinho expira em ${restante} minutos`);
restante -= 1;
if (restante === 0) {
clearInterval(id);
console.log('carrinho liberado');
}
}, 300);Tire o clearInterval e o mesmo código não para nunca. Deixei rodando dois
segundos antes de matar o processo à mão:
let restante = 3;
setInterval(() => {
console.log(`carrinho expira em ${restante} minutos`);
restante -= 1;
}, 300);Num script de terminal isso é chato. Num componente de interface é vazamento:
o componente sai da tela, o intervalo continua vivo, continua mexendo em estado
que não existe mais — e cada nova montagem cria mais um. Todo setInterval
precisa ter um lugar definido onde é cancelado, escrito no mesmo momento em
que o intervalo é criado.
O intervalo não desconta o tempo do seu código
O setInterval não dispara duas execuções ao mesmo tempo. Se o callback demora
mais que o intervalo, o período real vira o tempo do callback. Aqui o intervalo
é de 100ms e a recontagem leva bem mais que isso:
const inicio = Date.now();
let volta = 0;
const id = setInterval(() => {
volta += 1;
const entrou = Date.now() - inicio;
// recontagem de estoque, síncrona e pesada
let n = 0;
for (let i = 0; i < 400_000_000; i++) n += 1;
console.log(`volta ${volta}: entrou aos ${entrou}ms, saiu aos ${Date.now() - inicio}ms`);
if (volta === 4) clearInterval(id);
}, 100);Quatro voltas de um intervalo de 100ms deveriam terminar em 400ms; terminaram em
722ms. E repare que a volta 2 entrou 1ms depois de a volta 1 sair: o intervalo
virou “assim que der”. Se o seu código precisa de ritmo confiável, meça o tempo
decorrido com Date.now() dentro do callback em vez de multiplicar o número de
disparos pelo intervalo.
O erro clássico: chamar a função em vez de passar
Um par de parênteses a mais muda tudo. Em vez de entregar a função, você entrega o retorno dela:
function avisarSeparacao() {
console.log('separação liberada');
}
console.log('pedido pago');
setTimeout(avisarSeparacao(), 2000);
console.log('fim do handler');Leia a saída na ordem: separação liberada apareceu antes de fim do handler e sem esperar dois segundos — a função rodou na hora. Depois o
setTimeout recebeu undefined (o retorno dela) e reclamou. No navegador não
há erro nenhum: o setTimeout engole o valor inválido em silêncio, e você fica
com uma função que executou cedo e um agendamento que nunca acontece.
As duas formas corretas de passar argumentos:
function avisarSeparacao(pedido) {
console.log(`separação liberada para o pedido ${pedido}`);
}
console.log('pedido pago');
setTimeout(() => avisarSeparacao(8241), 300);
setTimeout(avisarSeparacao, 300, 8242);
console.log('fim do handler');Timer em aba inativa
Os navegadores limitam o que roda numa aba em segundo plano: timers passam a disparar no máximo uma vez por segundo, e depois de alguns minutos a economia fica mais agressiva ainda. É uma regra da plataforma, não um defeito do seu código — e ela quebra qualquer contagem de tempo feita somando disparos.
| você quer | não faça | faça |
|---|---|---|
| contar tempo | somar disparos de setInterval |
comparar dois Date.now() |
| animar | setTimeout recursivo |
requestAnimationFrame |
| esperar dado da API | setTimeout “generoso” |
await na Promise da requisição |
| repetir tarefa pesada | setInterval fixo |
reagendar com setTimeout no fim do trabalho |
A última linha da tabela é a mais útil: reagendar com setTimeout no fim do
próprio callback garante o intervalo entre execuções, sem nunca acumular
atraso nem empilhar trabalho.
Antes de escrever o próximo timer
- O atraso é mínimo. Se o número precisa ser exato, timer é a ferramenta errada.
- Todo
setIntervalnasce com oclearIntervaljá planejado. - Função sem parênteses no primeiro argumento. Argumentos extras vão do terceiro em diante, ou dentro de uma arrow.
- Timer não serve para esperar rede. Para isso existe Promise — assunto da próxima lição, onde a espera vira um valor que você pode passar adiante em vez de um palpite em milissegundos.
O mapa completo dos assuntos está no guia de JavaScript.
Perguntas frequentes
setTimeout de 0ms roda imediatamente?
Preciso guardar o retorno do setTimeout?
setInterval garante que minha função rode a cada N milissegundos?
Por que meu timer fica mais lento quando troco de aba?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — setTimeout() — developer.mozilla.org
- HTML Standard — Timers — html.spec.whatwg.org


