Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

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.

Rodolfo Mori8 min de leitura

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:

js
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));
comprou item 0 por 289.9 — sobra 210.10 comprou item 1 por 149.9 — sobra 60.20 comprou item 2 por 59.9 — sobra 0.30 parou com saldo de 0.30

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.

js
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');
processando pedido 1001 | restam 2 processando pedido 1002 | restam 1 processando pedido 1003 | restam 0 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:

js
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);
do...while rodou mesmo com a fila vazia

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:

js
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);
[ 'item 0', 'item 1', 'item 2' ] while parou em 3 | do...while parou em 3

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:

js
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);
falhou: timeout na transportadora (tentativa 1) falhou: timeout na transportadora (tentativa 2) sucesso na tentativa 3 { cep: '01310100', valor: 24.9 }

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:

js
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);
página 0: 3 pedidos página 1: 3 pedidos página 2: 1 pedidos página 3: 0 pedidos total carregado: 7

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:

js
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;
  }
}
processando pedido 1001 processando pedido 1001 processando pedido 1001 processando pedido 1001 processando pedido 1001 cinco voltas e a fila ainda tem 3 itens — este laço é infinito

fila[0] 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:

js
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`);
restante continua 3 depois de 35.098.200 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:

js
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');
antes do laço o laço travou tudo por 2s e deu muitas voltas depois do laço promise resolvida setTimeout de 0 ms

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:

js
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:

js
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);
}
5 864 10 27648 15 884736 20 28311552 file:///private/tmp/loja/w11.mjs:7 relatorio += relatorio; ^

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, ou Date.now() como orçamento de tempo, lançando erro ao estourar.
  • Nunca deixe a única saída depender de rede. Um while que 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:

js
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');
reservados 3 de 5 estoque restante: 0 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.

  • while
  • do while
  • laco
  • event loop
  • single thread

Perguntas frequentes

Quando usar while em vez de for?
Quando você não sabe quantas voltas serão necessárias. Percorrer um array é for; consumir uma fila que encolhe, paginar até a API devolver vazio ou tentar de novo até dar certo são while — a parada depende do resultado.
do while é usado de verdade?
É, em dois lugares: menu que precisa aparecer antes da primeira escolha e retentativa, onde a primeira chamada acontece de qualquer jeito. Fora disso, o while comum é mais previsível porque testa antes de agir.
Como eu paro um laço infinito que já travou a aba?
Não dá para parar pelo DevTools, porque ele também depende da mesma thread. Feche a aba pelo gerenciador de tarefas do navegador (Shift+Esc no Chrome). Em Node, Ctrl+C no terminal encerra o processo.
Um laço muito longo pode travar o servidor Node também?
Pode, e é pior: no navegador uma aba trava; num servidor Node o processo inteiro para de responder a todas as requisições enquanto o laço roda. Trabalho pesado vai para worker thread, fila ou processo separado.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

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

  1. MDN — while — developer.mozilla.org
  2. MDN — do...while — developer.mozilla.org

Continue por aqui