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

Operadores lógicos em JavaScript: &&, || e !

Os operadores && e || não devolvem booleano: devolvem um dos operandos. Curto-circuito, valor padrão, nullish e o bug do || que apaga o zero, no Node 24.

Rodolfo Mori6 min de leitura

O nome engana. && e || não devolvem true ou false — eles devolvem um dos dois operandos, sem converter nada. 'Ana' || 'visitante' devolve a string 'Ana'. Só o ! devolve booleano de verdade, sempre.

Entender isso é o que separa quem decora a && b de quem escreve config.tema || 'claro' sabendo exatamente o que vai sair do outro lado. É também a origem do bug mais comum desta lição: o valor padrão que apaga o zero.

Dois porteiros que devolvem o que encontraram

Pense em dois porteiros conferindo documentos da esquerda para a direita. O && procura o primeiro documento que barra a entrada; se nenhum barrar, entrega o último. O || procura o primeiro que já serve; quando encontra, entrega aquele e nem olha os seguintes.

Esse é o curto-circuito dos operadores lógicos. Eles avaliam apenas o necessário e devolvem um dos operandos originais, não obrigatoriamente um booleano. Na primeira sequência, circule o operando em que cada porteiro deve parar e depois compare com o console.log. O valor impresso mostra exatamente onde a avaliação terminou.

A regra mecânica: a && b avalia a; se a for falsy, devolve a e para. Se não, devolve b. O || faz o inverso: se a for truthy, devolve a; se não, devolve b.

js
const cupom = 'PRIMEIRACOMPRA';
const cupomVazio = '';

console.log(JSON.stringify(cupom && 'aplicado'));
console.log(JSON.stringify(cupomVazio && 'aplicado'));
console.log(JSON.stringify(cupom || 'SEMCUPOM'));
console.log(JSON.stringify(cupomVazio || 'SEMCUPOM'));
console.log(typeof (cupomVazio && 'aplicado'));
console.log(true && false);
"aplicado" "" "PRIMEIRACOMPRA" "SEMCUPOM" string false

O JSON.stringify está aí só para você enxergar as aspas: a segunda linha devolveu a string vazia, não false. E o typeof confirma — o resultado de um && entre duas strings é uma string. A última linha só sai booleana porque os dois operandos já eram booleanos.

Quais valores contam como falsy nessa decisão é uma lista fechada de oito, que a lição de truthy e falsy detalha.

Curto-circuito: o lado direito pode nem rodar

js
function consultarEstoque(sku) {
  console.log('  [consulta ao banco:', sku, ']');
  return 4;
}

const clienteLogado = false;

const disponivel = clienteLogado && consultarEstoque('TEC-001');
console.log('resultado:', disponivel);

const clienteAtivo = true;
const disponivel2 = clienteAtivo && consultarEstoque('TEC-001');
console.log('resultado:', disponivel2);
resultado: false [consulta ao banco: TEC-001 ] resultado: 4

Repare que a linha do banco não apareceu na primeira consulta. Com clienteLogado valendo false, o && já sabia a resposta e não chamou a função. Isso não é otimização automática do motor: é comportamento definido na especificação, e você pode contar com ele.

Consequência prática: a ordem dos operandos passa a ser lógica, não estética. usuario && usuario.pedidos.length funciona; usuario.pedidos.length && usuario quebra quando usuario é undefined.

O || como valor padrão

Este é o uso mais comum do || em código real:

js
function montarPedido(dados) {
  const cliente = dados.cliente || 'visitante';
  const frete = dados.frete || 24.9;
  const observacao = dados.observacao || 'sem observação';

  return { cliente, frete, observacao };
}

console.log(montarPedido({}));
console.log(montarPedido({ cliente: 'Ana', frete: 12.5 }));
{ cliente: 'visitante', frete: 24.9, observacao: 'sem observação' } { cliente: 'Ana', frete: 12.5, observacao: 'sem observação' }

Funciona perfeitamente — enquanto os valores reais forem truthy.

??: o padrão que respeita zero e string vazia

O ?? (coalescência nula) só entra quando o lado esquerdo é null ou undefined. Nada mais.

js
function montarPedido(dados) {
  const frete = dados.frete ?? 24.9;
  const observacao = dados.observacao ?? 'sem observação';
  return { frete, observacao };
}

console.log(montarPedido({ frete: 0, observacao: '' }));
console.log(montarPedido({}));
console.log(montarPedido({ frete: null }));
{ frete: 0, observacao: '' } { frete: 24.9, observacao: 'sem observação' } { frete: 24.9, observacao: 'sem observação' }

Frete zero sobreviveu. Observação vazia sobreviveu. null e ausência caíram no padrão. É esse o comportamento que quase todo formulário precisa.

valor à esquerda || usa o padrão? ?? usa o padrão?
undefined sim sim
null sim sim
0 sim não
'' sim não
false sim não
NaN sim não
'Ana' não não

! e a dupla negação

O ! é o único dos três que sempre devolve booleano. Duas negações em sequência convertem qualquer valor no booleano correspondente:

js
const carrinho = { itens: [], cupom: '' };

console.log(!carrinho.itens.length);
console.log(!carrinho.cupom);
console.log(!!carrinho.cupom);
console.log(!!carrinho.itens);
console.log(!!'0');
console.log(Boolean(carrinho.itens.length));
true true false true true false

A quarta linha é a que surpreende: !!carrinho.itens é true mesmo com o array vazio, porque um array é sempre truthy. !! e Boolean() fazem exatamente a mesma coisa; prefira Boolean() em código que outra pessoa vai ler.

&&=, ||= e ??=

As três atribuições lógicas chegaram no ES2021 e economizam um if:

js
const pedido = { cliente: 'Ana', frete: 0, cupom: null, observacao: '' };

pedido.cliente ||= 'visitante';
pedido.frete ??= 24.9;
pedido.cupom ??= 'SEMCUPOM';
pedido.observacao ||= 'sem observação';

console.log(pedido);
{ cliente: 'Ana', frete: 0, cupom: 'SEMCUPOM', observacao: 'sem observação' }

frete continuou 0 porque ??= só age em null/undefined. observacao virou o texto padrão porque ||= age em qualquer falsy — e string vazia é falsy. As duas linhas mostram a mesma decisão que você toma o tempo todo entre || e ??.

&& como guarda, e o que veio depois dele

Antes do encadeamento opcional, && era a forma padrão de acessar um campo aninhado com segurança:

js
const pedidos = [{ id: 1042, entrega: { cidade: 'Recife' } }, { id: 1043 }];

for (const pedido of pedidos) {
  const cidade = pedido.entrega && pedido.entrega.cidade;
  console.log(pedido.id, '->', cidade);
}

for (const pedido of pedidos) {
  console.log(pedido.id, '->', pedido.entrega?.cidade ?? 'retirada na loja');
}
1042 -> Recife 1043 -> undefined 1042 -> Recife 1043 -> retirada na loja

A segunda versão diz a mesma coisa com menos ruído: ?. para o acesso, ?? para o padrão. Você ainda vai encontrar a primeira em código existente — reconhecê-la é parte do trabalho.

Precedência: && vem antes de ||

js
const assinante = true;
const total = 100;
const cupomFrete = false;

console.log(assinante || total >= 199 && cupomFrete);
console.log(assinante || (total >= 199 && cupomFrete));
console.log((assinante || total >= 199) && cupomFrete);
true true false

A primeira e a segunda linha dão o mesmo resultado: o motor entende a || (b && c), porque && tem precedência maior. A terceira, com os parênteses onde muita gente acha que eles estão implícitos, dá o oposto. Numa regra de frete grátis, é a diferença entre cobrar e não cobrar. Escreva os parênteses.

Erros comuns

O || que apaga o zero e a string vazia

js
function montarPedido(dados) {
  const frete = dados.frete || 24.9;
  const observacao = dados.observacao || 'sem observação';
  return { frete, observacao };
}

console.log(montarPedido({ frete: 0, observacao: '' }));
{ frete: 24.9, observacao: 'sem observação' }

O cliente ganhou frete grátis — frete: 0 — e o sistema cobrou R$ 24,90 dele. Nenhum erro, nenhum log, nenhuma pista. Esse é o bug clássico do || como valor padrão, e ele aparece em toda base de código que nasceu antes do ?? existir.

O sintoma é sempre o mesmo: um valor legítimo que por acaso é falsy sendo trocado pelo padrão. Zero em campo numérico e string vazia em campo de texto são os dois casos que você vai encontrar de verdade. Isso vale também para parâmetros com valor default de função, um assunto tratado em funções em JavaScript.

O erro que lança: misturar ?? com ||

js
const config = {};

const frete = config.frete ?? 24.9 || 0;

console.log(frete);
file:///private/tmp/club-store/mistura-nullish.mjs:3 const frete = config.frete ?? 24.9 || 0; ^^

SyntaxError: Unexpected token ‘||’ 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) at afterLoad (node:internal/modules/esm/loader:491:29) at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:496:12) at #getOrCreateModuleJobAfterResolve (node:internal/modules/esm/loader:549:36) at afterResolve (node:internal/modules/esm/loader:597:52) at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:603:12) at node:internal/modules/esm/loader:622:32 at TracingChannel.tracePromise (node:diagnostics_channel:362:14)

Node.js v24.16.0

Nem chegou a rodar: é erro de sintaxe, apanhado na compilação do módulo. E é proposital. A especificação proíbe ?? sem parênteses ao lado de && ou || porque a precedência entre eles seria adivinhação para quem lê. Escreva (config.frete ?? 24.9) || 0 — ou, melhor, decida qual dos dois você realmente queria.

Quando usar cada um

  • && para condição composta e para guardar um acesso que pode quebrar.
  • || para padrão em que qualquer valor falsy deve mesmo ser substituído — cada vez mais raro.
  • ?? para padrão de campo que pode legitimamente valer 0, '' ou false. É a escolha certa na maioria dos formulários.
  • !/Boolean() para transformar em booleano de verdade, quando o valor vai para um JSON, um atributo de HTML ou uma comparação estrita.

A comparação que alimenta essas expressões é assunto da lição anterior desta trilha de JavaScript, == ou ===. Para ver onde && e || se encaixam no caminho completo de estudo, o guia de JavaScript mostra a sequência inteira.

  • operadores
  • logica
  • curto-circuito
  • nullish
  • valor padrao

Perguntas frequentes

Quando usar || e quando usar ?? para valor padrão?
Use ?? quando 0, string vazia e false forem valores legítimos que precisam passar — frete, desconto, quantidade, observação, qualquer campo numérico ou de texto livre. Use || só quando qualquer valor falsy realmente deva ser substituído, o que é mais raro do que parece.
e || sempre devolvem true ou false?
Não. Eles devolvem um dos dois operandos, sem converter. 'Ana' || 'anônimo' devolve a string 'Ana', não true. Só quando os dois lados já são booleanos é que o resultado sai booleano. Para forçar booleano, envolva em Boolean() ou use a dupla negação.
O que é curto-circuito e por que ele importa?
É a garantia de que o lado direito não é avaliado quando o esquerdo já decide o resultado. Isso importa porque o lado direito pode ser uma chamada cara, uma requisição, ou um acesso que quebraria — a ordem dos operandos deixa de ser detalhe de estilo e vira parte da lógica.
Posso misturar ?? com || na mesma expressão?
Não sem parênteses. O motor recusa com SyntaxError, de propósito, porque a precedência entre os dois seria ambígua para quem lê. Escreva (a ?? b) || c ou a ?? (b || c) e deixe explícito qual você quer.

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 — Operadores lógicos — developer.mozilla.org
  2. MDN — Operador de coalescência nula (??) — developer.mozilla.org
  3. ECMAScript 2026 Language Specification — Binary Logical Operators — tc39.es

Continue por aqui