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

switch case em JavaScript: como usar e quando usar

O switch compara com === estrito, então "1" não bate com case 1. Fall-through sem break, agrupamento de cases, o truque switch(true) e o erro do let solto.

Rodolfo Mori6 min de leitura

switch compara uma expressão contra uma lista de valores fixos e executa o bloco do primeiro que bater. É o formato que substitui a escada de else if quando todas as perguntas são sobre a mesma variável.

Duas regras explicam praticamente todo bug de switch: a comparação é estrita (===, sem conversão de tipo), e a execução não para sozinha no fim de um case — quem para é o break. Quem entende essas duas linhas não erra mais.

Continuamos na loja: status de pedido, forma de pagamento, prazo de entrega. Se você chegou aqui direto, if else em JavaScript cobre a versão com escada.

Pense num atendente com uma tabela de status

Um atendente recebe o status enviado e percorre uma tabela: se for pago, libera a separação; se for enviado, mostra o rastreio; se não reconhecer, chama o responsável. O switch formaliza esse balcão. A expressão é a ficha recebida, cada case é um valor conhecido e default é o caminho para aquilo que não estava na tabela.

O detalhe que a analogia não mostra é o fall-through: sem break, o JavaScript não volta ao balcão depois de encontrar uma linha; ele continua lendo as linhas seguintes. Faça uma previsão antes do primeiro exemplo: remova um break e anote quantas mensagens devem aparecer. Depois rode. É esse experimento, e não a tradução da palavra switch, que fixa a regra.

Anatomia

js
const status = 'enviado';

switch (status) {
  case 'aguardando':
    console.log('Pedido aguardando pagamento');
    break;
  case 'pago':
    console.log('Pagamento confirmado, separando no estoque');
    break;
  case 'enviado':
    console.log('Pedido a caminho');
    break;
  default:
    console.log(`Status desconhecido: ${status}`);
}
Pedido a caminho

O valor entre parênteses é avaliado uma vez. O motor percorre os case de cima para baixo comparando com ===, entra no primeiro que bate e executa dali para frente até encontrar break (ou o fim do switch). Nenhum case bateu? Executa o default.

A comparação é estrita — e é aqui que dói

switch usa ===. Não existe conversão de tipo, e é isso que quebra todo formulário que envia número como texto:

js
const idDaFormaDePagamento = '1';

switch (idDaFormaDePagamento) {
  case 1:
    console.log('Cartão de crédito');
    break;
  case 2:
    console.log('Pix');
    break;
  default:
    console.log('Forma de pagamento não reconhecida');
}

console.log(idDaFormaDePagamento == 1);
console.log(idDaFormaDePagamento === 1);
Forma de pagamento não reconhecida true false

A string '1' não bate com case 1. E repare no detalhe cruel: com == seria true. Um if (id == 1) que funcionava vira um switch que não funciona, e a mudança parecia puramente cosmética.

De onde vem a string: input.value no navegador, req.query no Express, req.params em rota, FormData.get(). Todos devolvem string, sempre, mesmo quando o campo é type="number".

js
const MENSAGENS = {
  aguardando: 'Pedido aguardando pagamento',
  pago: 'Pagamento confirmado, separando no estoque',
  enviado: 'Pedido a caminho',
  entregue: 'Pedido entregue',
};

function mensagemDoStatus(status) {
  return MENSAGENS[status] ?? `Status desconhecido: ${status}`;
}

console.log(mensagemDoStatus('pago'));
console.log(mensagemDoStatus('devolvido'));
console.log(mensagemDoStatus('toString'));
Pagamento confirmado, separando no estoque Status desconhecido: devolvido [Function: toString]

Guardei este exemplo para logo depois da comparação estrita porque ele mostra o outro lado da moeda. O objeto de consulta resolve o caso comum em uma linha — mas a última saída denuncia o preço: MENSAGENS['toString'] devolveu uma função herdada do prototype, e o ?? não pegou nada porque o valor não era null.

Com switch, 'toString' cairia no default sem drama. Se você preferir o objeto — e ele costuma ser a melhor escolha — crie-o com Object.create(null) ou verifique com Object.hasOwn(MENSAGENS, status).

Esquecer o break executa todos os cases seguintes

Este é o erro característico do switch, e ele não gera exceção nenhuma — só saída errada:

js
const status = 'pago';

switch (status) {
  case 'aguardando':
    console.log('Pedido aguardando pagamento');
  case 'pago':
    console.log('Pagamento confirmado, separando no estoque');
  case 'enviado':
    console.log('Pedido a caminho');
  case 'entregue':
    console.log('Pedido entregue');
  default:
    console.log('Fim do processamento');
}
Pagamento confirmado, separando no estoque Pedido a caminho Pedido entregue Fim do processamento

O pedido está apenas pago, e o programa anunciou que ele foi enviado e entregue. O switch entrou no case 'pago' — correto — e dali executou tudo para baixo, inclusive o default.

Repare que o case 'aguardando', que vem antes, não rodou. O case é um ponto de entrada, não um teste repetido: uma vez lá dentro, os rótulos seguintes são só rótulos.

Fall-through de propósito: agrupar cases

Quando você quer o comportamento em cascata, ele vira a forma mais limpa de agrupar valores. Vários case empilhados, um único bloco:

js
function prazoEmDias(uf) {
  switch (uf) {
    case 'SP':
    case 'RJ':
    case 'MG':
    case 'ES':
      return 2;
    case 'PR':
    case 'SC':
    case 'RS':
      return 4;
    case 'AM':
    case 'AC':
    case 'RR':
      return 12;
    default:
      return 7;
  }
}

console.log(prazoEmDias('SP'), prazoEmDias('RS'), prazoEmDias('AC'), prazoEmDias('BA'));
2 4 12 7

Aqui o return faz o papel do break: sai da função inteira, então não há como vazar para o grupo seguinte. Dentro de função, prefira return a break — o switch fica mais curto e o esquecimento deixa de ser possível.

js
function acaoDoStatus(status) {
  switch (status) {
    case 'pago':
      return 'separar no estoque';
    case 'enviado':
      return 'notificar o cliente';
    default:
      return 'nenhuma ação';
  }
}

console.log(acaoDoStatus('pago'));
console.log(acaoDoStatus('enviado'));
console.log(acaoDoStatus('cancelado'));
separar no estoque notificar o cliente nenhuma ação

O default não precisa ser o último

A busca pelo case correspondente é feita antes de qualquer coisa; o default só entra se nenhum bater, esteja ele onde estiver:

js
const status = 'cancelado';

switch (status) {
  default:
    console.log(`Status "${status}" caiu no default`);
    break;
  case 'pago':
    console.log('Pagamento confirmado');
    break;
  case 'enviado':
    console.log('Pedido a caminho');
    break;
}
Status "cancelado" caiu no default

Funciona — e mesmo assim escreva o default no fim. Não é regra da linguagem, é onde todo leitor procura. E cuidado: default no meio sem break vaza para o case de baixo como qualquer outro bloco.

Cada case compartilha o mesmo escopo

O switch inteiro é um bloco. Declarar let ou const dentro de dois case diferentes, sem chaves, colide:

js
const status = 'pago';

switch (status) {
  case 'pago':
    let mensagem = 'Pagamento confirmado';
    console.log(mensagem);
    break;
  case 'enviado':
    let mensagem = 'Pedido a caminho';
    console.log(mensagem);
    break;
}
file:///private/tmp/loja/s06.mjs:9 let mensagem = 'Pedido a caminho'; ^

SyntaxError: Identifier ‘mensagem’ has already been declared at compileSourceTextModule (node:internal/modules/esm/utils:318:16) at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:90:18) at #translate (node:internal/modules/esm/loader:435:20)

Node.js v24.16.0

SyntaxError: nada rodou. A indentação sugere dois escopos, mas existe um só. A solução é dar chaves ao case:

js
const status = 'pago';

switch (status) {
  case 'pago': {
    const mensagem = 'Pagamento confirmado';
    console.log(mensagem);
    break;
  }
  case 'enviado': {
    const mensagem = 'Pedido a caminho';
    console.log(mensagem);
    break;
  }
}
Pagamento confirmado

Agora cada case tem o próprio bloco, e os dois mensagem são variáveis distintas — o mesmo escopo de bloco que let e const trouxeram para o resto da linguagem.

switch (true): intervalos dentro de um switch

O switch compara valores, não faixas. Existe um truque conhecido para contornar isso: em vez de passar a variável, passe true e escreva a condição em cada case.

js
function faixaDeFrete(total) {
  switch (true) {
    case total >= 199:
      return 'grátis';
    case total >= 99:
      return 'R$ 9,90';
    case total >= 49:
      return 'R$ 14,90';
    default:
      return 'R$ 24,90';
  }
}

console.log(faixaDeFrete(250), faixaDeFrete(120), faixaDeFrete(60), faixaDeFrete(30));
grátis R$ 9,90 R$ 14,90 R$ 24,90

Funciona porque cada case agora é comparado — com === — contra true, e o primeiro que resultar verdadeiro entra. Como toda cadeia de faixas, a ordem é o algoritmo: inverta as três linhas e todo pedido cai na primeira faixa.

Minha opinião depois de ver isso em revisão algumas dezenas de vezes: o switch (true) só se paga quando você já tem um switch de valores no mesmo lugar e precisa acrescentar duas faixas. Para uma cadeia nova, três if com return dizem a mesma coisa sem exigir que o leitor decifre o truque.

Quando usar switch

a decisão é sobre use
a mesma variável contra 3+ valores fixos switch
a mesma variável contra 2 valores if / ternário
variáveis diferentes em cada pergunta if / else if
intervalos numéricos if com return, ou tabela de faixas
um mapa de chave para texto, sem lógica objeto com Object.hasOwn

E três hábitos que evitam a maior parte dos bugs desta lição: break (ou return) em todo case; um default que grita em vez de ficar calado — throw new Error(...) num status desconhecido é melhor que seguir em frente; e typeof no valor antes de culpar o switch.

A próxima lição da trilha de JavaScript sai das decisões e entra na repetição, com o laço for. O guia completo de JavaScript tem o mapa inteiro.

  • switch
  • case
  • break
  • fall-through
  • condicionais

Perguntas frequentes

Por que meu switch com número vindo de input não entra em nenhum case?
Porque input.value é sempre string e o switch compara com ===. A string "1" nunca bate com case 1. Converta antes, com Number(valor), ou escreva os cases como string. Misturar os dois é o que gera o bug intermitente.
O default precisa ser o último?
Não. O switch procura primeiro um case que bata e só cai no default se nenhum bater, independentemente da posição. Mas default no meio sem break cai no case seguinte — e por convenção ele fica no fim, onde todo mundo o procura.
Quando switch é melhor que if else?
Quando você compara a mesma variável contra muitos valores fixos e conhecidos: status de pedido, sigla de estado, forma de pagamento. Se as condições testam variáveis diferentes ou usam intervalos, o if else lê melhor.
Posso usar switch com objeto ou array no case?
Tecnicamente sim, mas nunca vai bater. A comparação é === e objetos são comparados por referência, não por conteúdo — só entra no case se for literalmente o mesmo objeto. Faça o switch sobre uma chave primitiva, como o id.

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 — switch — developer.mozilla.org
  2. MDN — Igualdade estrita (===) — developer.mozilla.org

Continue por aqui