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

Laço for em JavaScript: sintaxe, break e continue

As três partes do cabeçalho, let contra var no contador, break e continue, label em laço aninhado, for...of e o RangeError de estourar a pilha com spread.

Rodolfo Mori7 min de leitura

O for repete um bloco enquanto uma condição for verdadeira, controlando um contador que você mesmo declara. É o laço com mais controle da linguagem — e por isso também o que oferece mais formas de errar.

A resposta curta: for (let i = 0; i < lista.length; i++). As três partes, separadas por ponto e vírgula, são início, condição e passo. O início roda uma vez; a condição é checada antes de cada volta; o passo roda ao final de cada volta.

Continuamos na loja: itens do carrinho, estoque, pedidos do mês.

O for reúne três responsabilidades: inicialização, condição e incremento. Em palavras simples, ele define de onde a contagem parte, até quando continua e como avança depois de cada volta.

O roteiro de entregas com três marcações

Imagine um entregador com uma lista numerada: começa na casa zero, continua enquanto ainda houver endereço e risca um número ao terminar cada entrega. Se a posição inicial, a regra de parada ou o avanço estiver errado, a rota pula uma casa ou nunca termina. No for, essas três decisões ficam visíveis no cabeçalho, mas continuam sendo executadas em momentos diferentes.

Antes do primeiro laço, escreva os valores de i em uma coluna e marque em qual deles a condição deixa de passar. Depois compare com os logs. Essa tabela pequena detecta os dois erros clássicos — começar no índice errado e usar um limite que inclui uma volta a mais.

js
const itens = [
  { nome: 'Teclado mecânico', preco: 289.9 },
  { nome: 'Mouse sem fio', preco: 149.9 },
  { nome: 'Mousepad grande', preco: 59.9 },
];

let total = 0;

for (let i = 0; i < itens.length; i++) {
  console.log(`${i + 1}. ${itens[i].nome} — R$ ${itens[i].preco.toFixed(2)}`);
  total += itens[i].preco;
}

console.log('total:', total.toFixed(2));
1. Teclado mecânico — R$ 289.90 2. Mouse sem fio — R$ 149.90 3. Mousepad grande — R$ 59.90 total: 499.70

A ordem exata de execução é: let i = 0 uma vez; checa i < itens.length; corpo; i++; checa de novo; e assim por diante. Quando a condição dá false, o laço termina e o programa segue na linha seguinte.

O i começa em zero porque a primeira posição de um array é a zero. E a condição usa <, não <=: com três itens, os índices válidos são 0, 1 e 2 — i <= 3 tentaria ler itens[3], que é undefined.

De trás para frente e de dois em dois

Nada obriga o passo a ser +1. As três partes são expressões comuns:

js
const historico = ['criado', 'pago', 'separado', 'enviado', 'entregue'];

for (let i = historico.length - 1; i >= 0; i--) {
  console.log(i, historico[i]);
}

console.log('---');

for (let i = 0; i < historico.length; i += 2) {
  console.log(i, historico[i]);
}
4 entregue 3 enviado 2 separado 1 pago 0 criado --- 0 criado 2 separado 4 entregue

Percorrer de trás para frente não é enfeite: é o jeito correto de remover itens de um array durante o laço. Removendo de frente para trás, cada remoção puxa os elementos seguintes uma posição para a esquerda e o índice pula um item.

let e var no contador

Trocar let por var no cabeçalho não muda nada — até o momento em que o corpo do laço guarda uma função para usar depois. Na loja, isso é o handler de cada botão de cupom:

js
const cupons = ['PRIMEIRACOMPRA', 'FRETEGRATIS', 'BLACKFRIDAY'];

const comVar = [];
for (var i = 0; i < cupons.length; i++) {
  comVar.push(() => `botão ${i}: ${cupons[i]}`);
}

const comLet = [];
for (let j = 0; j < cupons.length; j++) {
  comLet.push(() => `botão ${j}: ${cupons[j]}`);
}

console.log(comVar.map((f) => f()));
console.log(comLet.map((f) => f()));
[ 'botão 3: undefined', 'botão 3: undefined', 'botão 3: undefined' ] [ 'botão 0: PRIMEIRACOMPRA', 'botão 1: FRETEGRATIS', 'botão 2: BLACKFRIDAY' ]

Os três botões do var aplicam o mesmo cupom inexistente. Com var existe uma única variável i, viva no escopo da função inteira; quando alguém clica, o laço já acabou e i vale 3.

Com let, o motor cria uma ligação nova a cada volta e copia o valor para ela. Cada função guardou o seu próprio j. O mecanismo é o mesmo descrito em variáveis em JavaScript — a diferença aqui é o sintoma: em vez de um console.log atrasado, é um botão que faz a coisa errada em produção.

break para tudo, continue pula uma volta

break encerra o laço na hora. Nenhuma volta a mais, nem o passo:

js
const itens = [
  { nome: 'Teclado mecânico', preco: 289.9, estoque: 4 },
  { nome: 'Mouse sem fio', preco: 149.9, estoque: 0 },
  { nome: 'Mousepad grande', preco: 59.9, estoque: 12 },
  { nome: 'Headset', preco: 399.0, estoque: 2 },
];

for (let i = 0; i < itens.length; i++) {
  if (itens[i].estoque === 0) {
    console.log(`sem estoque: ${itens[i].nome} — checkout bloqueado`);
    break;
  }
  console.log(`reservado: ${itens[i].nome}`);
}
reservado: Teclado mecânico sem estoque: Mouse sem fio — checkout bloqueado

O mousepad e o headset nunca foram avaliados. É esse o ponto: o break é a forma de dizer “já sei a resposta, pode parar”.

continue pula o resto do corpo e vai para o passo — a volta seguinte acontece normalmente:

js
const itens = [
  { nome: 'Teclado mecânico', preco: 289.9, estoque: 4 },
  { nome: 'Mouse sem fio', preco: 149.9, estoque: 0 },
  { nome: 'Mousepad grande', preco: 59.9, estoque: 12 },
  { nome: 'Headset', preco: 399.0, estoque: 2 },
];

let total = 0;

for (let i = 0; i < itens.length; i++) {
  if (itens[i].estoque === 0) {
    console.log(`pulando ${itens[i].nome}`);
    continue;
  }
  total += itens[i].preco;
}

console.log('total cobrável:', total.toFixed(2));
pulando Mouse sem fio total cobrável: 748.80

Laços aninhados e o label

break sai de um laço: o mais interno em que está. Para sair dos dois, dê nome ao laço externo:

js
const pedidos = [
  { id: 1001, itens: ['Teclado mecânico', 'Mouse sem fio'] },
  { id: 1002, itens: ['Cadeira gamer'] },
  { id: 1003, itens: ['Mousepad grande', 'Headset'] },
];

busca: for (let p = 0; p < pedidos.length; p++) {
  for (let i = 0; i < pedidos[p].itens.length; i++) {
    console.log('olhando', pedidos[p].id, pedidos[p].itens[i]);
    if (pedidos[p].itens[i] === 'Cadeira gamer') {
      console.log('achei no pedido', pedidos[p].id);
      break busca;
    }
  }
}

console.log('fim da busca');
olhando 1001 Teclado mecânico olhando 1001 Mouse sem fio olhando 1002 Cadeira gamer achei no pedido 1002 fim da busca

Sem o label, o break sairia só do laço de itens e o pedido 1003 seria varrido à toa. continue busca também existe, e manda o laço externo para a próxima volta.

Dito isso: label é um recurso que aparece pouco em código de time. A alternativa quase sempre melhor é extrair a busca para uma função e usar return — que sai de qualquer profundidade de aninhamento e ainda dá nome ao que o trecho faz.

for...of, for...in e entries()

Três formas parecidas, com resultados diferentes:

js
const itens = ['Teclado mecânico', 'Mouse sem fio', 'Mousepad grande'];
itens.promocao = true;

for (const nome of itens) console.log('for...of  ->', nome);
for (const chave in itens) console.log('for...in  ->', chave);
for (const [i, nome] of itens.entries()) console.log('entries   ->', i, nome);
for...of -> Teclado mecânico for...of -> Mouse sem fio for...of -> Mousepad grande for...in -> 0 for...in -> 1 for...in -> 2 for...in -> promocao entries -> 0 Teclado mecânico entries -> 1 Mouse sem fio entries -> 2 Mousepad grande

for...of percorre valores e ignora a propriedade promocao. for...in percorre chaves enumeráveis — e trouxe promocao junto, além de devolver os índices como string. entries() dá índice e valor de uma vez, que é o que normalmente se quer quando o índice importa.

Regra de bolso: for...of para array, for...in para objeto, entries() quando precisa dos dois. E for...of aceita break e continue — coisa que forEach não aceita. A comparação completa das formas de percorrer um array mede cada uma delas.

O for que nunca termina

Se o passo não aproxima o contador da condição de parada, o laço não acaba. Na prática, isso aparece de duas formas.

A primeira é o passo na direção errada. Em Node, ele costuma falhar rápido, e o erro não fala nada sobre laço:

js
const itens = [
  { nome: 'Teclado mecânico', preco: 289.9 },
  { nome: 'Mouse sem fio', preco: 149.9 },
  { nome: 'Mousepad grande', preco: 59.9 },
];

let total = 0;

for (let i = 0; i < itens.length; i--) {
  total += itens[i].preco;
}
file:///private/tmp/loja/f08.mjs:10 total += itens[i].preco; ^

TypeError: Cannot read properties of undefined (reading ‘preco’) at file:///private/tmp/loja/f08.mjs:10:21 at ModuleJob.run (node:internal/modules/esm/module_job:439:25)

Node.js v24.16.0

Segunda volta, i vale -1, itens[-1] é undefined, e ler .preco de undefined estoura. O erro é o TypeError: Cannot read properties of undefined, mas a causa não está na linha 10, e sim no i-- do cabeçalho, na linha logo acima. Vale como hábito: num erro dentro de laço, leia primeiro o cabeçalho.

A segunda forma é pior: o passo simplesmente não existe, e o corpo não toca em nada. Aí não há erro nenhum — o processo fica girando. Este exemplo mede quantas voltas isso dá em um segundo antes de eu forçar a saída:

js
const itens = ['Teclado mecânico', 'Mouse sem fio', 'Mousepad grande'];

let voltas = 0;
const fim = Date.now() + 1000;

for (let i = 0; i < itens.length; ) {
  voltas++;
  if (Date.now() > fim) break;
}

console.log(`em 1 segundo o laço deu ${voltas.toLocaleString('pt-BR')} voltas no mesmo item`);
em 1 segundo o laço deu 35.510.056 voltas no mesmo item

Trinta e cinco milhões de voltas no primeiro item, sem sair do lugar. No navegador, sem o break de segurança, é exatamente isso que produz a aba congelada e o aviso “a página não está respondendo” — assunto da próxima lição.

Erros comuns: RangeError: Maximum call stack size exceeded

Este aparece quando alguém troca um for por spread para concatenar arrays grandes:

js
const pedidosDoMes = Array.from({ length: 200000 }, (_, i) => ({ id: i, total: 99.9 }));

const todos = [];
todos.push(...pedidosDoMes);

console.log(todos.length);
file:///private/tmp/loja/f09.mjs:4 todos.push(...pedidosDoMes); ^

RangeError: Maximum call stack size exceeded at file:///private/tmp/loja/f09.mjs:4:7 at ModuleJob.run (node:internal/modules/esm/module_job:439:25)

Node.js v24.16.0

O ... transforma cada item do array em um argumento de push. Duzentos mil argumentos numa chamada só não cabem na pilha, e o motor derruba com RangeError. Com 200 itens o mesmo código funciona — é por isso que o bug sobrevive ao ambiente de desenvolvimento e só aparece com dado de produção.

O for de sempre não tem esse teto:

js
const pedidosDoMes = Array.from({ length: 200000 }, (_, i) => ({ id: i, total: 99.9 }));

const todos = [];
for (let i = 0; i < pedidosDoMes.length; i++) {
  todos.push(pedidosDoMes[i]);
}

console.log(todos.length);
200000

Alternativas sem laço explícito: todos = todos.concat(pedidosDoMes) ou todos = [...todos, ...pedidosDoMes] — as duas criam um array novo em vez de empilhar argumentos, e nenhuma delas estoura.

Qual laço usar

você precisa de use
índice, passo customizado ou percurso reverso for clássico
só os valores, com break ou continue for...of
índice e valor juntos for (const [i, v] of arr.entries())
percorrer as chaves de um objeto for...in ou Object.entries()
transformar cada item num outro map
ficar com um subconjunto filter
reduzir tudo a um número reduce
parar no primeiro que satisfaz find / some

O contador manual é a ferramenta mais poderosa e a que mais exige atenção: é onde moram o i <= length, o passo invertido e o var do closure. Quando o for...of resolve, ele é a escolha mais segura.

A próxima lição da trilha de JavaScript é while e do...while — repetição sem contador, onde a condição de parada depende de você mudar alguma coisa dentro do corpo. O guia completo de JavaScript mostra o percurso inteiro.

Prefere aprender em vídeo?

Tem uma aula sobre este assunto no nosso canal.

Ver todos os vídeos do canal
  • for
  • laco
  • break
  • continue
  • for of

Perguntas frequentes

for clássico ou forEach?
forEach lê melhor quando você processa todos os itens sem parar no meio. O for clássico e o for...of ganham quando precisa de break, continue, do índice controlado por você ou de percorrer de trás para frente.
Por que não devo usar for...in em array?
Porque ele percorre chaves enumeráveis, não posições. Num array isso inclui qualquer propriedade que alguém tenha pendurado nele, e as chaves vêm como string — "0", não 0. Para array, use for...of ou o for clássico.
Dá para usar break dentro de forEach?
Não. break não é válido dentro de um callback, e return só encerra aquela iteração — o forEach continua. Se precisa parar no meio, troque por for...of, ou use some, find ou every, que param sozinhos.
O que é aquele rótulo antes do for?
É um label. Ele dá nome ao laço para que break nome ou continue nome atinjam o laço externo, não o interno. É útil em busca dentro de matriz; fora disso, extrair uma função com return costuma ler melhor.

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 — for — developer.mozilla.org
  2. MDN — Rótulo (label) — developer.mozilla.org

Continue por aqui