Sua dose diária de programação

Conceito, 3 exemplos e 3 exercícios: uma pequena conquista de cada vez.

✦   Lição

Decisões com condicionais

Condicionais deixam o programa escolher um caminho. O if executa um bloco quando a condição é verdadeira; else if testa outra condição se a anterior falhou, e else cobre o que sobrou. As condições são expressões boolean, montadas com comparações (===, !==, <, >=) e combinadas com && (e), || (ou) e ! (não).

TypeScript Fundamentos Iniciante 3 exemplos + 3 exercícios
42 lições publicadas 10 linguagens novas lições todo dia útil
TypeScript / Fundamentos

Decisões com condicionais

por Sutton Ashford
Iniciante

Entenda o conceito

Condicionais deixam o programa escolher um caminho. O if executa um bloco quando a condição é verdadeira; else if testa outra condição se a anterior falhou, e else cobre o que sobrou. As condições são expressões boolean, montadas com comparações (===, !==, <, >=) e combinadas com && (e), || (ou) e ! (não).

Para comparar, use sempre === e !==: eles conferem valor e tipo, sem as conversões escondidas de ==. Quando a decisão é só escolher entre dois valores, o ternário condicao ? a : b cabe numa linha. Quando um mesmo valor é comparado com várias opções fixas, o switch organiza os casos, e case empilhados compartilham o mesmo bloco.

O TypeScript acrescenta duas ajudas. Com um tipo união de literais (type Status = 'pago' | 'enviado'), o tsc recusa qualquer texto fora da lista. E com o narrowing, um teste como typeof bonus === 'number' faz o compilador saber o tipo exato da variável dentro do if; num switch, o tipo never no default avisa quando algum caso ficou esquecido.

↳ Pontos-chave
  • Em if / else if / else, só o primeiro bloco cuja condição é verdadeira roda; a ordem dos testes importa.
  • Compare com === e !==; == converte tipos por baixo dos panos (0 == '' é true).
  • && exige as duas condições, || aceita uma delas e ! inverte um boolean.
  • O ternário cond ? a : b escolhe um valor; o switch compara um valor com vários case, e cada case termina em return ou break.
  • Depois de typeof x === 'number', o TypeScript trata x como number dentro do bloco (narrowing).

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01Ponto do bolo com if, else if e else

forno.ts
// Ponto do bolo: if, else if e else com comparações e operadores lógicos.

const receita: string = 'Bolo de fubá';
const temperatura: number = 180; // graus do forno
const minutosNoForno: number = 35;
const palitoSaiLimpo: boolean = true;

console.log(`Receita: ${receita}`);

// if testa uma condição; else if só é testado se a anterior falhou.
if (temperatura < 160) {
  console.log('Forno frio demais: o bolo vai solar.');
} else if (temperatura > 200) {
  console.log('Forno quente demais: vai queimar por fora.');
} else {
  console.log('Temperatura ideal.');
}

// && exige as duas condições verdadeiras.
if (minutosNoForno >= 30 && palitoSaiLimpo) {
  console.log('Pronto: pode tirar do forno.');
} else {
  console.log('Ainda não: espere mais alguns minutos.');
}

// || basta uma das condições ser verdadeira.
const temOvos: boolean = true;
const temLeite: boolean = false;
if (!temOvos || !temLeite) {
  console.log('Falta ingrediente: confira a lista de compras.');
}

// === compara valor e tipo, sem conversões escondidas.
if (receita === 'Bolo de fubá' && temOvos) {
  console.log('Dá para assar outra fornada amanhã.');
}

O primeiro if escolhe uma entre três mensagens: o else if só é testado se a temperatura não for baixa, e o else pega o que sobrou (de 160 a 200 graus). Como 180 não cai nos dois primeiros testes, aparece "Temperatura ideal.".

Os blocos seguintes combinam condições: && exige tempo suficiente e palito limpo, || dispara o aviso se faltar ovo ou leite, e ! inverte cada boolean. A comparação receita === 'Bolo de fubá' usa ===, que confere valor e tipo sem conversões automáticas.

02Status do pedido com switch e ternário

pedido.ts
// Status de um pedido da loja: união de literais, switch e ternário.

// O tipo Status só aceita estes quatro textos.
type Status = 'pago' | 'enviado' | 'entregue' | 'cancelado';

function mensagem(status: Status): string {
  switch (status) {
    case 'pago':
      return 'pagamento confirmado, separando os itens';
    case 'enviado':
      return 'pedido a caminho';
    case 'entregue':
      return 'pedido entregue';
    case 'cancelado':
      return 'pedido cancelado, valor estornado';
  }
}

// Cases empilhados compartilham o mesmo bloco.
function podeCancelar(status: Status): boolean {
  switch (status) {
    case 'pago':
    case 'enviado':
      return true;
    default:
      return false;
  }
}

function mostrar(numero: number, status: Status): void {
  // Ternário: condição ? valor se verdadeiro : valor se falso.
  const cancelar = podeCancelar(status) ? 'sim' : 'não';
  console.log(`#${numero} [${status}] ${mensagem(status)} | pode cancelar? ${cancelar}`);
}

mostrar(101, 'pago');
mostrar(102, 'enviado');
mostrar(103, 'entregue');
mostrar(104, 'cancelado');

// mostrar(105, 'perdido'); // erro: '"perdido"' não é atribuível a 'Status'

O tipo Status é uma união de literais: só os quatro textos listados são aceitos, e a chamada comentada com 'perdido' não passa no tsc. Em mensagem, cada case termina com return; como todos os quatro valores estão cobertos, o compilador sabe que a função sempre devolve uma string e não exige um return no fim.

Em podeCancelar, os case empilhados 'pago' e 'enviado' caem no mesmo bloco, e o default responde pelos demais. Na função mostrar, o ternário transforma o boolean em 'sim' ou 'não' numa única linha.

03Jogo de dados com narrowing e never

dados.ts
// Jogo de dados: narrowing com typeof e switch exaustivo com never.

// O bônus pode ser um número (+3) ou um texto ('dobro').
function aplicarBonus(rolagem: number, bonus: number | string): number {
  // Dentro deste if, o TypeScript sabe que bonus é number.
  if (typeof bonus === 'number') {
    return rolagem + bonus;
  }
  // Aqui só sobrou string.
  return bonus === 'dobro' ? rolagem * 2 : rolagem;
}

type Resultado = 'critico' | 'acerto' | 'erro';

function classificar(total: number): Resultado {
  if (total >= 20) return 'critico';
  if (total >= 10) return 'acerto';
  return 'erro';
}

function narrar(resultado: Resultado): string {
  switch (resultado) {
    case 'critico':
      return 'Golpe crítico!';
    case 'acerto':
      return 'Acertou o alvo.';
    case 'erro':
      return 'Errou...';
    default: {
      // Se surgir um Resultado novo sem case, o tsc acusa erro aqui.
      const impossivel: never = resultado;
      return impossivel;
    }
  }
}

function jogada(jogador: string, rolagem: number, bonus: number | string): void {
  const total = aplicarBonus(rolagem, bonus);
  console.log(
    `${jogador}: dado ${rolagem}, bônus ${bonus}, total ${total} -> ${narrar(classificar(total))}`,
  );
}

jogada('Lia', 12, 3);
jogada('Téo', 11, 'dobro');
jogada('Rui', 4, 'nenhum');
jogada('Ana', 9, 0);

O parâmetro bonus é number | string. Fora do if, não dá para somá-lo a um número; depois de typeof bonus === 'number', o TypeScript estreita o tipo e libera rolagem + bonus. Abaixo do if, que sempre retorna, só pode sobrar string, e o ternário trata o caso 'dobro'.

classificar usa if com return antecipado, que dispensa o else. Em narrar, o default atribui o valor a uma variável never: hoje esse ponto é inalcançável, mas, se alguém acrescentar 'empate' ao tipo Resultado sem criar o case, o tsc acusa o erro nessa linha.


Agora é com você

Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.

01Frete por faixa de valorFácil

Uma loja cobra frete conforme o valor da compra: a partir de R$ 200,00 é grátis, a partir de R$ 100,00 custa R$ 10,00 e abaixo disso custa R$ 20,00. Escreva a função frete e imprima o resumo de quatro compras: 250, 120, 45.50 e 200.

↳ Requisitos
  • Criar function frete(compra: number): number com if / else if / else.
  • Mostrar "frete grátis" quando o frete for zero, escolhendo o texto com o operador ternário.
  • Exibir os valores com toFixed(2); a saída deve ser exatamente a esperada.
código inicial
function frete(compra: number): number {
  // a partir de 200: grátis; a partir de 100: 10; abaixo: 20
  return 0;
}

function resumo(compra: number): void {
  // calcule o frete, escolha o texto e imprima a linha
}

resumo(250);
resumo(120);
resumo(45.5);
resumo(200);
saída esperada
Compra de R$ 250.00: frete grátis, total R$ 250.00
Compra de R$ 120.00: frete R$ 10.00, total R$ 130.00
Compra de R$ 45.50: frete R$ 20.00, total R$ 65.50
Compra de R$ 200.00: frete grátis, total R$ 200.00
Ver dica

Teste a faixa mais alta primeiro: se compra >= 100 vier antes, uma compra de 250 também cai nela.

Ver resposta
// Frete da loja por faixa de valor da compra.
function frete(compra: number): number {
  // As faixas vão da maior para a menor: a primeira que passar vence.
  if (compra >= 200) {
    return 0;
  } else if (compra >= 100) {
    return 10;
  } else {
    return 20;
  }
}

function resumo(compra: number): void {
  const valorFrete = frete(compra);
  // Ternário escolhe o texto do frete.
  const textoFrete = valorFrete === 0 ? 'frete grátis' : `frete R$ ${valorFrete.toFixed(2)}`;
  const total = compra + valorFrete;
  console.log(`Compra de R$ ${compra.toFixed(2)}: ${textoFrete}, total R$ ${total.toFixed(2)}`);
}

resumo(250);
resumo(120);
resumo(45.5);
resumo(200);

Passo a passo

  1. frete recebe o valor da compra e devolve o frete como number. A anotação ): number faz o tsc reclamar se algum caminho ficar sem return.
  2. O primeiro teste é compra >= 200, a faixa mais alta. Se ele falhar, o else if testa compra >= 100, e o else cobre o resto.
  3. Em resumo, o frete é calculado uma vez e guardado em valorFrete, para não chamar a função duas vezes.
  4. O ternário valorFrete === 0 ? 'frete grátis' : ... escolhe o texto: com ===, a comparação é exata entre dois números.
  5. total soma a compra e o frete, e toFixed(2) formata os três valores em reais só na hora de imprimir.
  6. A compra de 200 mostra o limite: >= inclui o próprio 200 na faixa grátis.

Por que funciona

Numa cadeia if / else if / else, só o primeiro bloco verdadeiro roda. Ordenar as faixas da maior para a menor garante que cada compra caia na faixa certa sem precisar escrever intervalos completos como compra >= 100 && compra < 200: se o programa chegou ao else if, já se sabe que a compra é menor que 200.

Erros comuns

  • Inverter a ordem: com if (compra >= 100) primeiro, a compra de 250 paga R$ 10,00 de frete, porque o teste de 200 nunca é alcançado. Teste a faixa mais alta antes.
  • Usar > no lugar de >=: compra > 200 cobra frete da compra de exatamente 200, e a última linha sai errada.
  • Imprimir o valor sem formatar: R$ ${compra} mostra R$ 45.5 e R$ 250, diferente da saída esperada. Use toFixed(2) em todos os valores em reais.

Outra forma de resolver

Como cada ramo só devolve um valor, dá para usar if com return antecipado e dispensar os else: if (compra >= 200) return 0; if (compra >= 100) return 10; return 20;. O resultado é o mesmo e fica mais curto; a cadeia com else deixa explícito que os casos são exclusivos.

Saída esperada

Compra de R$ 250.00: frete grátis, total R$ 250.00
Compra de R$ 120.00: frete R$ 10.00, total R$ 130.00
Compra de R$ 45.50: frete R$ 20.00, total R$ 65.50
Compra de R$ 200.00: frete grátis, total R$ 200.00

02A biblioteca está aberta?Médio

A biblioteca abre de segunda a sexta das 8h às 20h, no sábado das 9h às 13h e fica fechada no domingo. Crie um tipo para os dias da semana e uma função que diga se ela está aberta num dia e numa hora cheia.

↳ Requisitos
  • Declarar type Dia como união dos sete dias ('segunda' a 'domingo', com acento em 'terça' e 'sábado').
  • Criar function estaAberta(dia: Dia, hora: number): boolean com switch, agrupando os dias úteis em case empilhados.
  • Testar o horário com &&, considerando que no horário de fechar (20h, 13h) ela já está fechada.
  • Imprimir "aberta" ou "fechada" com o operador ternário; a saída deve ser exatamente a esperada.
código inicial
type Dia = 'segunda' | 'terça'; // complete com os outros dias

function estaAberta(dia: Dia, hora: number): boolean {
  // use switch com os dias úteis agrupados
  return false;
}

function consultar(dia: Dia, hora: number): void {
  // imprima: <dia> às <hora>h: aberta | fechada
}

consultar('segunda', 19);
saída esperada
segunda às 19h: aberta
quarta às 7h: fechada
sexta às 20h: fechada
sábado às 12h: aberta
sábado às 15h: fechada
domingo às 10h: fechada
Ver dica

Uma hora está dentro do expediente quando é maior ou igual à abertura e menor que o fechamento.

Ver resposta
// Horário da biblioteca: switch com cases agrupados e um tipo união.
type Dia = 'segunda' | 'terça' | 'quarta' | 'quinta' | 'sexta' | 'sábado' | 'domingo';

function estaAberta(dia: Dia, hora: number): boolean {
  switch (dia) {
    // Dias úteis compartilham o mesmo horário: cases empilhados.
    case 'segunda':
    case 'terça':
    case 'quarta':
    case 'quinta':
    case 'sexta':
      return hora >= 8 && hora < 20;
    case 'sábado':
      return hora >= 9 && hora < 13;
    case 'domingo':
      return false;
  }
}

function consultar(dia: Dia, hora: number): void {
  const situacao = estaAberta(dia, hora) ? 'aberta' : 'fechada';
  console.log(`${dia} às ${hora}h: ${situacao}`);
}

consultar('segunda', 19);
consultar('quarta', 7);
consultar('sexta', 20);
consultar('sábado', 12);
consultar('sábado', 15);
consultar('domingo', 10);

Passo a passo

  1. type Dia lista os sete textos aceitos. Com ele, consultar('sabado', 12) (sem acento) é erro no tsc, e não um dia que cai silenciosamente no caso errado.
  2. Os cinco dias úteis são case empilhados sem código entre eles: qualquer um dos cinco chega ao mesmo return.
  3. hora >= 8 && hora < 20 exige as duas condições. O < (e não <=) faz a hora de fechar já contar como fechada.
  4. O sábado tem a própria regra, e o domingo devolve false direto.
  5. Não há default: os sete valores do tipo estão cobertos, e o tsc sabe que a função sempre devolve um boolean.
  6. Em consultar, o ternário converte o boolean em 'aberta' ou 'fechada' antes de imprimir.

Por que funciona

O switch compara o mesmo valor (dia) com várias opções fixas, situação em que ele é mais legível que uma cadeia de if com ||. O tipo união fecha a lista de opções: o compilador confere tanto as chamadas (nenhum dia inventado) quanto o switch (nenhum dia esquecido, senão a função poderia terminar sem return).

Erros comuns

  • Esquecer um dia no switch, por exemplo o 'domingo': o tsc acusa TS2366 ("Function lacks ending return statement and return type does not include 'undefined'"). Cubra todos os valores ou acrescente um default.
  • Usar hora <= 20: a consulta de sexta às 20h sai "aberta", diferente da saída esperada.
  • Trocar && por ||: hora >= 8 || hora < 20 é verdadeiro para qualquer hora, e a biblioteca nunca fecha nos dias úteis.

Outra forma de resolver

Uma cadeia de if resolve o mesmo: if (dia === 'domingo') return false; if (dia === 'sábado') return hora >= 9 && hora < 13; return hora >= 8 && hora < 20;. É mais curta aqui, mas o switch fica mais claro quando cada grupo de dias tem uma regra diferente e a lista de casos cresce.

Saída esperada

segunda às 19h: aberta
quarta às 7h: fechada
sexta às 20h: fechada
sábado às 12h: aberta
sábado às 15h: fechada
domingo às 10h: fechada

03Situação no boletimDesafio

Monte o boletim de cinco alunos com duas notas e uma prova final, que pode ter nota ou a marca 'ausente' (vale zero). A situação segue esta ordem: frequência abaixo de 75% reprova por falta; senão, média a partir de 7 aprova, a partir de 5 vai para recuperação e abaixo disso reprova.

↳ Requisitos
  • Tratar a prova final como number | 'ausente' e obter a nota com narrowing por typeof.
  • Criar type Situacao com 'aprovado', 'recuperacao', 'reprovado' e 'reprovado-por-falta', e a função situacao(media, frequencia) com if / else if / else.
  • Converter a situação em texto com switch e um default que atribui o valor a uma variável never.
  • Dados: Ana 8, 7.5, 9 (90%); Bruno 6, 5, 4.5 (85%); Caio 9, 8, ausente (95%); Duda 9, 9.5, 10 (60%); Eva 3, 4, ausente (80%).
código inicial
type Situacao = 'aprovado' | 'recuperacao' | 'reprovado' | 'reprovado-por-falta';

function notaDaFinal(final: number | 'ausente'): number {
  // use typeof para separar os dois casos
  return 0;
}

function situacao(media: number, frequencia: number): Situacao {
  // a falta vem antes da média
  return 'reprovado';
}

console.log('Boletim do 1º bimestre');
saída esperada
Boletim do 1º bimestre
Ana: notas 8, 7.5, 9 | média 8.2 | freq. 90% -> Aprovado
Bruno: notas 6, 5, 4.5 | média 5.2 | freq. 85% -> Recuperação
Caio: notas 9, 8, ausente | média 5.7 | freq. 95% -> Recuperação
Duda: notas 9, 9.5, 10 | média 9.5 | freq. 60% -> Reprovado por falta
Eva: notas 3, 4, ausente | média 2.3 | freq. 80% -> Reprovado
Ver dica

A ordem dos testes decide o resultado: a Duda tem média 9.5, mas a regra da frequência precisa ser conferida antes.

Ver resposta
// Boletim: narrowing com typeof, if/else if e switch exaustivo com never.
type Situacao = 'aprovado' | 'recuperacao' | 'reprovado' | 'reprovado-por-falta';

// A prova final pode ter nota ou a marca 'ausente'.
function notaDaFinal(final: number | 'ausente'): number {
  // Depois do typeof, o TypeScript sabe que final é number.
  if (typeof final === 'number') {
    return final;
  }
  return 0; // ausência conta como zero
}

function situacao(media: number, frequencia: number): Situacao {
  // A falta vem primeiro: reprova mesmo com média alta.
  if (frequencia < 75) {
    return 'reprovado-por-falta';
  } else if (media >= 7) {
    return 'aprovado';
  } else if (media >= 5) {
    return 'recuperacao';
  } else {
    return 'reprovado';
  }
}

function descrever(s: Situacao): string {
  switch (s) {
    case 'aprovado':
      return 'Aprovado';
    case 'recuperacao':
      return 'Recuperação';
    case 'reprovado':
      return 'Reprovado';
    case 'reprovado-por-falta':
      return 'Reprovado por falta';
    default: {
      // Um caso novo em Situacao sem case aqui vira erro no tsc.
      const impossivel: never = s;
      return impossivel;
    }
  }
}

function boletim(
  aluno: string,
  nota1: number,
  nota2: number,
  final: number | 'ausente',
  frequencia: number,
): void {
  const media = (nota1 + nota2 + notaDaFinal(final)) / 3;
  const texto = descrever(situacao(media, frequencia));
  console.log(
    `${aluno}: notas ${nota1}, ${nota2}, ${final} | média ${media.toFixed(1)} | freq. ${frequencia}% -> ${texto}`,
  );
}

console.log('Boletim do 1º bimestre');
boletim('Ana', 8, 7.5, 9, 90);
boletim('Bruno', 6, 5, 4.5, 85);
boletim('Caio', 9, 8, 'ausente', 95);
boletim('Duda', 9, 9.5, 10, 60);
boletim('Eva', 3, 4, 'ausente', 80);

Passo a passo

  1. Situacao é uma união de quatro literais. Ela documenta todos os resultados possíveis e permite que o tsc confira o switch mais adiante.
  2. notaDaFinal recebe number | 'ausente'. Dentro do if (typeof final === 'number'), o TypeScript sabe que final é number e deixa devolvê-lo; fora dele, só resta 'ausente', que vale zero.
  3. situacao testa a frequência antes da média, porque a falta reprova independentemente da nota. Depois, as faixas de média vão da maior para a menor.
  4. descrever usa switch para transformar cada situação no texto exibido. Os valores do tipo usam hífen e não têm acento; o texto para o usuário fica num só lugar.
  5. No default, const impossivel: never = s; só compila porque todos os casos foram tratados antes e nada sobra para s.
  6. boletim calcula a média com parênteses em volta da soma e chama as duas funções, encadeando descrever(situacao(...)).
  7. O template literal mostra final como está: 9 ou ausente. A média aparece com uma casa decimal por toFixed(1), mas a comparação usa o valor exato.

Por que funciona

Cada função faz uma escolha: notaDaFinal separa os dois tipos possíveis da prova, situacao decide com uma cadeia em que só o primeiro teste verdadeiro vale, e descrever mapeia valores fixos com switch. O narrowing evita conversões manuais: o compilador acompanha o typeof e só deixa usar final como número no trecho em que isso é garantido.

O never transforma o switch num contrato. Se a escola criar a situação 'aprovado-com-louvor' e alguém esquecer o case, o tsc acusa "Type '"aprovado-com-louvor"' is not assignable to type 'never'" na linha do default, antes de o boletim sair com um texto faltando.

Erros comuns

  • Testar a média antes da frequência: a Duda (média 9.5, frequência 60%) sai "Aprovado". Coloque frequencia < 75 no primeiro if.
  • Somar final direto: (nota1 + nota2 + final) / 3 dá erro no tsc (TS2365: o operador + não se aplica a number e a uma união que inclui texto). Passe antes por notaDaFinal.
  • Comparar com o texto do usuário: case 'Recuperação': em descrever é erro TS2678, porque esse literal não faz parte de Situacao. Use os valores do tipo.
  • Esquecer um case e manter o default com never: é exatamente o erro que ele existe para mostrar. Acrescente o case que falta, não remova a checagem.

Outra forma de resolver

notaDaFinal cabe numa linha com o ternário: return final === 'ausente' ? 0 : final;. A comparação com o literal também estreita o tipo, então final é number no segundo ramo. Para descrever, um objeto Record<Situacao, string> com os quatro textos substituiria o switch; ele é preferível quando cada caso só devolve um valor fixo, enquanto o switch acomoda lógica diferente em cada ramo.

Saída esperada

Boletim do 1º bimestre
Ana: notas 8, 7.5, 9 | média 8.2 | freq. 90% -> Aprovado
Bruno: notas 6, 5, 4.5 | média 5.2 | freq. 85% -> Recuperação
Caio: notas 9, 8, ausente | média 5.7 | freq. 95% -> Recuperação
Duda: notas 9, 9.5, 10 | média 9.5 | freq. 60% -> Reprovado por falta
Eva: notas 3, 4, ausente | média 2.3 | freq. 80% -> Reprovado

Últimas lições

Ver o arquivo completo →