Truthy e falsy em JavaScript: os 8 valores falsos
A lista fechada dos oito valores falsy, por que array vazio e objeto vazio são truthy, e o if que sempre passa — com cada conversão executada no Node 24.
Todo valor em JavaScript vira true ou false quando entra num if, num &&
ou num while. Os que viram false são oito, e só oito: false, 0, -0,
0n, '', null, undefined e NaN. Todo o resto é truthy.
Decorar essa lista é mais útil do que parece, porque ela é curta e fechada. A
consequência que pega todo mundo é o outro lado: array vazio e objeto vazio são
truthy, então if (carrinho.itens) passa mesmo com o carrinho zerado.
O porteiro do if trabalha com uma lista curta
Pense num porteiro que precisa responder apenas “entra” ou “não entra”. O
JavaScript entrega qualquer valor a ele — número, texto, objeto — e o porteiro
faz a coerção booleana, isto é, converte temporariamente aquele valor em
true ou false para tomar a decisão. A lista dos que não entram é fechada:
são os oito valores falsy. Todo o resto passa.
O valor original não vira booleano para sempre; a conversão vale naquele
contexto. É por isso que if (carrinho.itens) pode entrar e ainda deixar você
com um array vazio: o porteiro só viu que existe um objeto. Antes de seguir,
classifique 0, '0', '', [] e {} sem executar. Depois confira com
Boolean(valor). Os pares que parecem iguais visualmente, mas recebem decisões
diferentes, são justamente os que causam bugs de formulário e carrinho.
A lista fechada dos oito falsy
const falsy = [
['false', false],
['0', 0],
['-0', -0],
['0n', 0n],
["'' (string vazia)", ''],
['null', null],
['undefined', undefined],
['NaN', NaN],
];
for (const [rotulo, valor] of falsy) {
console.log(rotulo.padEnd(18), '->', Boolean(valor));
}Três deles são variações de zero (0, -0, 0n), dois são ausência (null,
undefined), um é o resultado de conta inválida (NaN), um é a string sem
caracteres e um é o próprio false. Não há mais nada.
Todo o resto é truthy — inclusive o que parece vazio
const suspeitos = [
['[] (array vazio)', []],
['{} (objeto vazio)', {}],
["'0'", '0'],
["'false'", 'false'],
["' ' (só espaço)", ' '],
['-1', -1],
['Infinity', Infinity],
['function () {}', function () {}],
];
for (const [rotulo, valor] of suspeitos) {
console.log(rotulo.padEnd(18), '->', Boolean(valor));
}A regra por trás disso é simples: todo objeto é truthy, sem exceção, e array
e função são objetos. Para string, o critério é ter pelo menos um caractere —
'0', 'false' e ' ' têm. Para número, o critério é ser diferente de zero e
não ser NaN; -1 e Infinity passam.
Boolean() e a dupla negação
Duas formas de forçar a conversão, com resultado idêntico:
const digitado = '';
const digitadoZero = '0';
const digitadoTexto = 'três';
console.log(Boolean(digitado), Boolean(Number(digitado)));
console.log(Boolean(digitadoZero), Boolean(Number(digitadoZero)));
console.log(Boolean(digitadoTexto), Boolean(Number(digitadoTexto)));
console.log(Number(''), Number('0'), Number('três'));A segunda linha é o resumo da lição: a string '0' é truthy, e o número 0 que
ela vira é falsy. Um campo de quantidade que chega como texto muda de
comportamento dependendo de você ter convertido antes ou não. Sobre por que
Number('') é 0 e não NaN, a lição de
operadores aritméticos mostra a mesma
conversão acontecendo dentro das contas.
O filter(Boolean) e o que ele leva junto
Passar Boolean direto para o filter é um idioma que todo mundo reconhece —
mas ele filtra os oito falsy, não só os que você tinha em mente:
const cupons = ['PRIMEIRACOMPRA', '', null, 'FRETEGRATIS', undefined, 0, 'BLACKFRIDAY'];
console.log(cupons.filter(Boolean));
console.log(cupons.filter((c) => c).length);
const quantidades = [3, 0, 2, 0, 1];
console.log(quantidades.filter(Boolean));
console.log(quantidades.filter((q) => q !== 0));Nos cupons, o resultado é o desejado. Nas quantidades, as duas linhas coincidem por acaso — e a segunda diz o que quer dizer. Quando o array pode conter zero como valor válido, escreva a condição explícita.
Zero é um valor, não uma ausência
Numa loja, 0 aparece o tempo todo como resultado legítimo: frete grátis,
desconto nenhum, estoque zerado. Todo if que trata o zero como “não veio”
esconde informação do cliente:
function linhaDoResumo(pedido) {
if (pedido.desconto) {
return `desconto de R$ ${pedido.desconto.toFixed(2)}`;
}
return 'sem desconto';
}
console.log(linhaDoResumo({ desconto: 30 }));
console.log(linhaDoResumo({ desconto: 0 }));
console.log(linhaDoResumo({}));
function linhaCorreta(pedido) {
if (pedido.desconto !== undefined) {
return `desconto de R$ ${pedido.desconto.toFixed(2)}`;
}
return 'sem desconto';
}
console.log(linhaCorreta({ desconto: 0 }));As linhas 2 e 3 saíram iguais para situações diferentes: um pedido em que o
desconto foi calculado e deu zero, e um pedido em que o campo nem existe. A
versão de baixo separa os dois casos com uma comparação explícita — e é a mesma
distinção que separa || de ?? em
operadores lógicos.
Erros comuns
if (carrinho.itens) é sempre verdadeiro
const carrinho = { cliente: 'Ana', itens: [] };
if (carrinho.itens) {
console.log('o carrinho tem itens');
} else {
console.log('carrinho vazio');
}
console.log('quantidade:', carrinho.itens.length);O programa anunciou que o carrinho tem itens e em seguida contou zero. A
condição testou se carrinho.itens existe, não se ele tem conteúdo — e um
array vazio existe. Num checkout real, isso é o botão “Finalizar compra”
habilitado num carrinho sem nada dentro.
A correção é dizer o que você quer medir:
const carrinho = { cliente: 'Ana', itens: [] };
if (carrinho.itens.length > 0) {
console.log('o carrinho tem itens');
} else {
console.log('carrinho vazio');
}
console.log(Boolean(carrinho.itens));
console.log(Boolean(carrinho.itens.length));As duas últimas linhas mostram o par lado a lado: o array é truthy, o length
dele é falsy. Testar o length é o hábito a pegar.
O objeto vazio que passa no if e quebra na linha seguinte
Este é o mesmo erro com consequência pior, porque agora ele lança:
const pedido = { id: 1042, entrega: {} };
if (pedido.entrega) {
console.log('endereço cadastrado, montando etiqueta');
console.log(pedido.entrega.cidade.toUpperCase());
}TypeError: Cannot read properties of undefined (reading ‘toUpperCase’) at file:///private/tmp/club-store/etiqueta.mjs:5:37 at ModuleJob.run (node:internal/modules/esm/module_job:439:25) at async node:internal/modules/esm/loader:633:26 at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v24.16.0
A primeira linha imprimiu: o if passou, porque {} é truthy. Só que
pedido.entrega.cidade é undefined, e undefined.toUpperCase() derruba o
processo. A guarda deu uma falsa sensação de segurança — que é o pior tipo de
guarda. Esse rastro é o mesmo do
TypeError: Cannot read properties of undefined,
o erro mais comum da linguagem.
Duas correções, dependendo do que você quer:
const pedido = { id: 1042, entrega: {} };
const temEndereco = Object.keys(pedido.entrega).length > 0;
console.log('tem endereço?', temEndereco);
console.log(pedido.entrega.cidade?.toUpperCase() ?? 'RETIRADA NA LOJA');Object.keys(obj).length responde “esse objeto tem alguma coisa dentro”. O
encadeamento opcional com ?? responde “me dê isso ou um substituto”. A escolha
entre ?? e || aqui importa, e é o assunto de
operadores lógicos.
Objetos empacotados: new Boolean(false) é truthy
Raro, mas quando aparece consome uma tarde:
console.log(Boolean(new Boolean(false)));
console.log(Boolean(''), Boolean(new String('')));
console.log(Boolean(0), Boolean(new Number(0)));
console.log(typeof new Boolean(false));new Boolean(false) não é o valor false: é um objeto que embrulha false
— e todo objeto é truthy. O mesmo vale para new String('') e new Number(0).
Nunca use os construtores new Boolean, new String ou new Number. Sem o
new, Boolean(x), String(x) e Number(x) são funções de conversão perfeitas.
Checklist para escrever a condição certa
| o que você quer saber | condição errada | condição certa |
|---|---|---|
| o array tem itens | if (itens) |
if (itens.length > 0) |
| o objeto tem campos | if (dados) |
if (Object.keys(dados).length) |
| o campo foi preenchido | if (nome) |
if (nome.trim() !== '') |
| o número foi informado | if (quantidade) |
if (quantidade !== undefined) |
| a conta deu certo | if (total) |
if (!Number.isNaN(total)) |
| o elemento existe no DOM | — | if (elemento) é o correto aqui |
A última linha é a exceção honesta: document.querySelector devolve null
quando não acha, e null é falsy, então testar o valor direto é idiomático.
Fora desse caso, prefira dizer o que está medindo.
Um bom hábito de leitura: sempre que encontrar um if com uma variável solta
dentro, pergunte “quais dos oito falsy podem chegar aqui?”. Se a resposta incluir
um valor legítimo do seu domínio — um frete zero, um comentário vazio — a
condição está errada mesmo que o teste esteja passando. A lição de
variáveis em JavaScript mostra o outro lado disso:
o valor que ainda não existe versus o valor que existe e está vazio. O
guia de JavaScript encadeia os dois assuntos no caminho
completo.
Perguntas frequentes
Por que array vazio é truthy se ele não tem nada dentro?
A lista de valores falsy pode crescer com uma versão nova?
Usar !! para converter é considerado ruim?
document.querySelector devolve falsy quando não acha?
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 — Falsy — developer.mozilla.org
- MDN — Truthy — developer.mozilla.org
- ECMAScript 2026 Language Specification — ToBoolean — tc39.es


