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.
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.
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);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
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);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:
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 }));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.
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 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:
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));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:
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);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:
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');
}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 ||
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);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
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: '' }));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 ||
const config = {};
const frete = config.frete ?? 24.9 || 0;
console.log(frete);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
&¶ 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 valer0,''oufalse. É a escolha certa na maioria dos formulários.!/Boolean()para transformar em booleano de verdade, quando o valor vai para umJSON, 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.
Perguntas frequentes
Quando usar || e quando usar ?? para valor padrão?
e || sempre devolvem true ou false?
O que é curto-circuito e por que ele importa?
Posso misturar ?? com || na mesma expressão?
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 — Operadores lógicos — developer.mozilla.org
- MDN — Operador de coalescência nula (??) — developer.mozilla.org
- ECMAScript 2026 Language Specification — Binary Logical Operators — tc39.es


