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.
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.
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));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:
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]);
}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:
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()));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:
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}`);
}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:
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));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:
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');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:
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 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:
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;
}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:
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`);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:
const pedidosDoMes = Array.from({ length: 200000 }, (_, i) => ({ id: i, total: 99.9 }));
const todos = [];
todos.push(...pedidosDoMes);
console.log(todos.length);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:
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);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.
Perguntas frequentes
for clássico ou forEach?
Por que não devo usar for...in em array?
Dá para usar break dentro de forEach?
O que é aquele rótulo antes do for?
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 — for — developer.mozilla.org
- MDN — Rótulo (label) — developer.mozilla.org



