while e do while em JavaScript: laços com condição
Quando o número de voltas não é conhecido: fila, paginação e retentativa. Por que do while roda ao menos uma vez e como um laço congela a aba inteira.
while repete um bloco enquanto uma condição for verdadeira. Diferente do
laço for, ele não tem contador embutido: quem
muda o estado que faz a condição virar falsa é o corpo do laço.
A escolha entre os dois é sobre o que você sabe. Se você sabe quantas voltas
serão, é for. Se a parada depende do que acontecer lá dentro, é while.
Fila que esvazia, API que pagina, tentativa que falha e repete: nenhum desses
tem número de voltas conhecido antes de começar.
E existe uma terceira forma, do...while, que testa a condição depois de
executar o corpo — por isso roda pelo menos uma vez, sempre.
Lave pratos enquanto a pia não estiver vazia
Você não precisa saber quantos pratos existem para começar a lavar. Olha a pia,
lava um, retira da fila e pergunta de novo se ainda sobrou. Esse é o modelo do
while: testar uma condição, executar o bloco e mudar alguma coisa para que
o teste possa um dia ficar falso.
A comparação expõe também o risco. Se você lava, mas nunca tira o prato da pia, a condição não muda e o trabalho vira infinito. No código, isso bloqueia a thread inteira. Antes do primeiro laço, escreva ao lado dele qual valor será alterado em cada volta e qual situação encerra a repetição. Se você não consegue apontar os dois, não execute ainda. Esse hábito simples evita mais loops infinitos que qualquer limite colocado depois do susto.
Repetir enquanto a condição segurar
O cliente tem um vale-compras e vai levando itens enquanto o saldo cobrir:
let saldoDoVale = 500;
let itemDaVez = 0;
const precos = [289.9, 149.9, 59.9, 399.0];
while (itemDaVez < precos.length && saldoDoVale >= precos[itemDaVez]) {
saldoDoVale -= precos[itemDaVez];
console.log(`comprou item ${itemDaVez} por ${precos[itemDaVez]} — sobra ${saldoDoVale.toFixed(2)}`);
itemDaVez++;
}
console.log('parou com saldo de', saldoDoVale.toFixed(2));Quatro itens no array, três comprados. O laço parou porque R$ 0,30 não paga o
headset — e não porque acabou a lista. Um for sobre precos teria percorrido
os quatro; foi a condição de negócio que decidiu.
Repare na ordem dentro do &&: o limite do array vem antes do preço. As duas
partes de um && são avaliadas da esquerda para a direita, e a segunda só roda se
a primeira for verdadeira — então precos[itemDaVez] nunca é lido fora do array.
Invertendo, o último teste leria precos[4], que é undefined, e a comparação
daria false por acidente em vez de por regra.
Consumindo uma fila que encolhe
O caso mais comum de while no dia a dia: processar até acabar, quando “acabar”
é o próprio corpo que provoca.
const fila = ['pedido 1001', 'pedido 1002', 'pedido 1003'];
while (fila.length > 0) {
const pedido = fila.shift();
console.log('processando', pedido, '| restam', fila.length);
}
console.log('fila vazia');shift() remove o primeiro item e devolve ele. A cada volta a fila fica menor,
até fila.length > 0 virar falso. Repare que aqui um for com índice daria
errado: o array muda de tamanho durante o percurso, e o índice ficaria
dessincronizado.
do...while roda pelo menos uma vez
Esta é a diferença inteira entre as duas formas, e ela aparece quando a condição já nasce falsa:
const filaVazia = [];
while (filaVazia.length > 0) {
console.log('while rodou');
}
do {
console.log('do...while rodou mesmo com a fila vazia');
} while (filaVazia.length > 0);O while testou primeiro, viu false e não executou nada. O do...while
executou o corpo e só então testou — daí a linha impressa.
Quando a condição já é verdadeira desde o começo, os dois se comportam igual:
const carrinho = { itens: [], limite: 3 };
let i = 0;
do {
carrinho.itens.push(`item ${i}`);
i++;
} while (i < carrinho.limite);
console.log(carrinho.itens);
let j = 0;
while (j < carrinho.limite) {
j++;
}
console.log('while parou em', j, '| do...while parou em', i);Onde o do...while realmente ganha: retentativa
Chamar uma transportadora que às vezes dá timeout. A primeira tentativa acontece de qualquer jeito — não faz sentido testar “já falhou?” antes de tentar:
function consultarFrete(cep, tentativa) {
if (tentativa < 3) throw new Error(`timeout na transportadora (tentativa ${tentativa})`);
return { cep, valor: 24.9 };
}
let tentativa = 0;
let resposta = null;
do {
tentativa++;
try {
resposta = consultarFrete('01310100', tentativa);
console.log('sucesso na tentativa', tentativa);
} catch (e) {
console.log('falhou:', e.message);
}
} while (resposta === null && tentativa < 5);
console.log(resposta);Duas coisas fazem esse laço ser seguro: a condição tem duas saídas
(resposta chegou ou o teto de tentativas estourou), e a variável tentativa
muda em toda volta, antes de qualquer coisa que possa lançar exceção.
while (true) com break: paginação
Quando a condição de parada só é conhecida no meio do corpo, escrever true e
sair com break é mais honesto do que inventar uma variável de controle:
const BANCO = Array.from({ length: 7 }, (_, i) => ({ id: 1000 + i, total: 99.9 }));
function buscarPagina(pagina, tamanho) {
const inicio = pagina * tamanho;
return BANCO.slice(inicio, inicio + tamanho);
}
let pagina = 0;
const todos = [];
while (true) {
const lote = buscarPagina(pagina, 3);
console.log(`página ${pagina}: ${lote.length} pedidos`);
if (lote.length === 0) break;
todos.push(...lote);
pagina++;
}
console.log('total carregado:', todos.length);O laço só descobre que acabou quando a chamada volta vazia — e isso é depois de buscar. Tentar escrever essa condição no cabeçalho exigiria uma primeira busca fora do laço, duplicando a linha.
A condição precisa mudar dentro do corpo
Este é o erro estrutural do while, e não existe equivalente no for — lá o
passo mora no cabeçalho e é difícil esquecer. Aqui, o corpo lê a fila mas nunca
a esvazia:
const fila = ['pedido 1001', 'pedido 1002', 'pedido 1003'];
let voltas = 0;
while (fila.length > 0) {
const pedido = fila[0];
console.log('processando', pedido);
voltas++;
if (voltas === 5) {
console.log('cinco voltas e a fila ainda tem', fila.length, 'itens — este laço é infinito');
break;
}
}fila[0] lê o primeiro item; fila.shift() remove. Um caractere de
diferença na versão que funciona, e o laço passa a girar no mesmo pedido para
sempre. Sem o contador de segurança, não haveria nem essa saída — só a
ausência dela.
Sem break nenhum, o laço não erra: ele gira. Este exemplo mede quantas voltas
isso dá em um segundo:
let restante = 3;
let voltas = 0;
const fim = Date.now() + 1000;
while (restante > 0) {
voltas++;
if (Date.now() > fim) break;
}
console.log(`restante continua ${restante} depois de ${voltas.toLocaleString('pt-BR')} voltas em 1 segundo`);Trinta e cinco milhões de voltas, nenhum progresso, nenhuma mensagem de erro. É por isso que laço infinito é mais difícil de diagnosticar que exceção: ele não produz sintoma nenhum além do silêncio.
Single-thread: o laço congela a página inteira
JavaScript executa numa única thread. Enquanto o seu while estiver rodando,
nada mais acontece: nem clique, nem renderização, nem setTimeout, nem a
resposta de um fetch que já chegou. Tudo fica na fila esperando o laço
devolver o controle.
Dá para provar isso no Node:
console.log('antes do laço');
setTimeout(() => console.log('setTimeout de 0 ms'), 0);
Promise.resolve().then(() => console.log('promise resolvida'));
const fim = Date.now() + 2000;
let voltas = 0;
while (Date.now() < fim) {
voltas++;
}
console.log('o laço travou tudo por 2s e deu', voltas > 0 ? 'muitas voltas' : 'nenhuma volta');
console.log('depois do laço');O setTimeout foi agendado para 0 ms e imprimiu por último, mais de dois
segundos depois. A promise, que tem prioridade sobre o timer, também esperou. O
laço síncrono ficou com a thread inteira até terminar.
No navegador, o mesmo código dentro de um handler de clique produz o efeito clássico:
document.querySelector('#calcular-frete').addEventListener('click', () => {
let total = 0;
let i = 0;
while (i < pedidos.length) {
total += pedidos[i].valor;
// faltou o i++
}
document.querySelector('#total').textContent = total;
});A partir do clique: o cursor não muda mais, o botão fica com o estado de
pressionado, nenhuma animação CSS avança, o console.log que você colocou não
aparece — porque desenhar no console também precisa da thread. Depois de alguns
segundos o Chrome mostra a caixa “A página não está respondendo” com as opções
Sair e Aguardar, e o Firefox mostra “Um script na página pode estar ocupado”.
Erros comuns: quando o laço infinito consegue estourar
Nem todo laço sem fim gira em silêncio. Quando o corpo acumula memória, o motor derruba o processo — e o erro não menciona laço nenhum:
const linha = 'pedido 1001;faturado;99.90\n';
let relatorio = linha;
let voltas = 0;
while (true) {
relatorio += relatorio;
voltas++;
if (voltas % 5 === 0) console.log(voltas, relatorio.length);
}RangeError: Invalid string length at file:///private/tmp/loja/w11.mjs:7:16 at ModuleJob.run (node:internal/modules/esm/module_job:439:25)
Node.js v24.16.0
O while (true) ficou sem break, e a string dobrou de tamanho a cada volta até
passar do limite do V8, um pouco acima de 500 milhões de caracteres. O
RangeError fala de string, não de laço — o defeito real é a condição de parada
que nunca foi escrita.
Três defesas que valem para qualquer while:
- Escreva a mudança de estado antes de escrever o corpo.
shift(),i++,pagina++— a linha que faz a condição evoluir vem primeiro. - Todo
while (true)nasce com um teto. Contador máximo de voltas, ouDate.now()como orçamento de tempo, lançando erro ao estourar. - Nunca deixe a única saída depender de rede. Um
whileque espera uma resposta que pode não vir precisa de teto de tentativas, como no exemplo de retentativa acima.
Escolhendo entre while, do…while e for
| a repetição é | forma |
|---|---|
| sobre os itens de um array | for...of ou for |
| um número de voltas conhecido | for |
| até uma fila esvaziar | while (fila.length > 0) |
| até a API devolver página vazia | while (true) com break e teto |
| tentar até dar certo, com teto | do...while |
| um menu que precisa aparecer antes da escolha | do...while |
O último exemplo junta as duas ideias da lição — condição de negócio e mudança de estado no corpo — numa reserva de estoque parcial:
const estoque = { 'Teclado mecânico': 3 };
let reservados = 0;
const pedido = { produto: 'Teclado mecânico', quantidade: 5 };
while (reservados < pedido.quantidade && estoque[pedido.produto] > 0) {
estoque[pedido.produto]--;
reservados++;
}
console.log(`reservados ${reservados} de ${pedido.quantidade}`);
console.log('estoque restante:', estoque[pedido.produto]);
console.log(reservados === pedido.quantidade ? 'pedido completo' : 'pedido parcial');Duas condições ligadas por &&, duas variáveis mudando no corpo, e a saída dizendo
qual das duas parou o laço. É o formato de while que eu escreveria em produção.
Daqui, a trilha de JavaScript segue para arrays e
objetos, onde map, filter e reduce
substituem boa parte dos laços que você escreveu até aqui. O guia completo de JavaScript mostra
onde cada assunto entra, e a categoria
front-end reúne o resto do material.
Perguntas frequentes
Quando usar while em vez de for?
do while é usado de verdade?
Como eu paro um laço infinito que já travou a aba?
Um laço muito longo pode travar o servidor Node também?
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 — while — developer.mozilla.org
- MDN — do...while — developer.mozilla.org


