null, undefined e NaN: qual a diferença em JS
Os dois vazios do JavaScript e o número que não é número: quando usar cada um, por que null == undefined é true e por que NaN nunca é igual a NaN.
JavaScript tem dois valores para representar “nada” e um para representar “esta
conta não deu número”. Em uma linha cada: undefined é ausência que a linguagem
produziu, null é ausência que você escolheu, e NaN é o resultado de uma
operação numérica que falhou.
const pedido = { id: 4821, cupom: null };
console.log(pedido.cupom);
console.log(pedido.observacao);
console.log(Number('dois'));Os três são valores legítimos, nenhum deles lança erro sozinho, e é exatamente por isso que eles atravessam o programa até quebrar longe da origem. Esta lição mostra de onde cada um vem, como distinguir e como se defender.
Três etiquetas para três ausências diferentes
Imagine uma ficha de pedido. null é o campo “cupom” preenchido de propósito
com “não tem”. undefined é o campo “observação” que nem chegou a ser
preenchido. NaN é outra situação: havia um campo para número, mas alguém
tentou transformar a palavra “dois” em quantidade e a conta não produziu um
número válido.
Na linguagem, os três continuam sendo valores distintos. null é uma ausência
intencional, undefined costuma indicar que nada foi atribuído e NaN pertence
ao tipo number, embora marque uma operação numérica inválida. Antes de avançar,
preveja qual das três etiquetas aparece em cada console.log acima e confira a
ordem na saída real.
undefined não é algo que você escreve — é o que sobra. Quatro origens cobrem
praticamente todos os casos:
let codigoDeRastreio;
const pedido = { id: 4821 };
function registrarItem(item) {}
console.log(codigoDeRastreio);
console.log(pedido.entregue);
console.log([289.9, 149.9][5]);
console.log(registrarItem('Teclado mecânico'));Na ordem: variável declarada e não inicializada; propriedade que o objeto não
tem; posição de array fora do tamanho; função que não tem return. Some a isso o
parâmetro que a chamada não passou, e a lista está completa.
Quatro origens diferentes com a mesma cara é o que torna o undefined difícil de
rastrear. Quando um aparecer onde não deveria, a pergunta útil não é “o que é
undefined” — é qual dessas quatro aconteceu.
null é escolha; undefined é ausência
null você digita. Ele significa “aqui não tem nada, e isso é intencional”: o
pedido não tem cupom, a entrega não tem data marcada, o cliente não informou
telefone. A diferença entre os dois parece filosófica até você serializar o
objeto:
const comNull = { id: 4821, cupom: null, entregaEm: null };
const comUndefined = { id: 4821, cupom: undefined, entregaEm: undefined };
console.log(JSON.stringify(comNull));
console.log(JSON.stringify(comUndefined));
console.log(Object.keys(comUndefined));Olhe a segunda linha. JSON.stringify apagou as duas propriedades que valiam
undefined — mas o Object.keys da terceira linha prova que elas ainda existem
no objeto. O que saiu pela rede não é o objeto que você tinha.
Isso vira bug real em API de atualização parcial: mandar cupom: null é dizer
“remova o cupom”; mandar cupom: undefined é dizer “não mexi no cupom”, porque o
campo nem chega ao servidor. Se você já apanhou de um PATCH que não removia
nada, era isto.
Igual com dois sinais diz sim; com três, diz não
console.log(null == undefined);
console.log(null === undefined);
console.log(null == 0);
console.log(undefined == 0);
console.log(null >= 0);A primeira linha é uma exceção escrita à mão na especificação: na comparação
frouxa, null e undefined são iguais entre si e a mais nada. Não há
conversão para número no meio — tanto que null == 0 é false.
A última linha é a pegadinha que essa exceção deixa: null >= 0 é true, porque
operador de ordem não tem exceção nenhuma e converte null para 0. Ou seja,
null não é igual a zero, mas é maior ou igual a zero. Não tente encontrar
lógica; apenas não compare null com número.
O uso legítimo do == mora justo aí:
const pedido = { id: 4821, cupom: null };
console.log(pedido.cupom == null);
console.log(pedido.observacao == null);
console.log(pedido.id == null);x == null é a forma curta e idiomática de perguntar “está vazio, de qualquer um
dos dois jeitos?”. É a única exceção que vale abrir na regra de usar sempre
===.
NaN não é igual a si mesmo
NaN significa Not a Number e, ironicamente, é do tipo number. Ele nasce
quando uma operação numérica não tem resultado numérico possível:
const total = Number('R$ 289,90');
console.log(total);
console.log(NaN === NaN);
console.log([NaN].indexOf(NaN));
console.log([NaN].includes(NaN));
console.log(Object.is(NaN, NaN));NaN === NaN é false. Isso vem do padrão IEEE 754, que todas as linguagens com
ponto flutuante seguem: dois resultados inválidos não têm por que ser o mesmo
resultado inválido.
A consequência prática está nas duas linhas do meio. indexOf usa comparação
estrita e não acha o NaN que está claramente dentro do array; includes usa
outra regra de igualdade e acha. Mesmo array, mesma pergunta, respostas
diferentes — e é o tipo de detalhe que faz um filtro devolver lista errada sem
ninguém entender por quê.
isNaN global mente; Number.isNaN não
console.log(isNaN('289,90'));
console.log(Number.isNaN('289,90'));
console.log(isNaN(''));
console.log(Number.isNaN(Number('')));
console.log(Number.isNaN(0 / 0));As duas primeiras linhas fazem a mesma pergunta e respondem o contrário. O
isNaN global converte o argumento para número antes de testar: '289,90'
vira NaN na conversão, então ele responde true. Ele não está dizendo que o
valor é NaN; está dizendo que o valor viraria NaN.
Number.isNaN não converte nada. Só responde true para o próprio NaN. Como as
duas perguntas são úteis em situações diferentes, o jeito de não errar é traduzir
o nome ao ler:
| escrito | leia como | responde true para |
|---|---|---|
isNaN(v) |
“isto não daria um número?” | 'abc', undefined, {}, NaN |
Number.isNaN(v) |
“isto é o próprio NaN?” | só NaN |
Number.isFinite(v) |
“isto é número e cabe na reta?” | 289.9, 0, -1 |
Na dúvida, Number.isNaN(Number(v)) responde a pergunta que você normalmente
quer fazer: “converti, e não deu número”.
Erros comuns
O erro que os três produzem é sempre o mesmo, e é o mais frequente da linguagem: você acessa uma propriedade de um valor que não tem propriedades.
const pedido = { id: 4821, cliente: 'Ana Souza' };
console.log(pedido.itens.length);TypeError: Cannot read properties of undefined (reading ‘length’) at file:///private/tmp/loja/l6-length.mjs:3:26 Node.js v24.16.0
Leia a mensagem em duas partes. A palavra depois de properties of diz o que
estava vazio: undefined aqui, null se o objeto tivesse itens: null. E o
nome entre parênteses diz o que você tentou ler. Junte as duas e você tem o
diagnóstico: pedido.itens não existe, e a culpa não é do .length.
O detalhe que engana é o dedo apontando para length na linha do erro, e não
para itens. O passo a passo completo desse diagnóstico está em
TypeError: Cannot read properties of undefined.
Os outros dois tropeços:
- Testar
if (valor)achando que testa existência.0,''eNaNtambém são falsos. Uma quantidade zero legítima some da tela por causa disso. - Comparar com
NaNusando===. Nunca dátrue. UseNumber.isNaN.
Defesa: ?., ?? e o valor padrão
Três operadores resolvem quase todos os casos, e é onde esta lição vira código do dia a dia:
const pedido = { id: 4821, cupom: null };
console.log(pedido.itens?.length);
console.log(pedido.itens?.length ?? 0);
console.log(pedido.cupom ?? 'sem cupom');O ?. interrompe o acesso e devolve undefined em vez de lançar. O ?? fornece
um valor de reserva só quando o lado esquerdo é null ou undefined — e essa
precisão é a diferença dele para o ||:
const frete = 0;
console.log(frete ?? 24.9);
console.log(frete || 24.9);0 é um frete válido: entrega grátis. O ?? respeita; o || o considera falso e
troca por 24,90. Todo bug de “o desconto zero virou o desconto padrão” é um ||
no lugar de um ??.
Para o NaN, a defesa é converter e conferir na entrada:
function totalSeguro(valor) {
const numero = Number(valor);
return Number.isNaN(numero) ? 0 : numero;
}
console.log(totalSeguro('289.90'), totalSeguro('R$ 289,90'), totalSeguro(null));Converter na borda do sistema — assim que o dado chega do formulário ou da API —
é o que impede o NaN de viajar. A próxima lição da trilha é exatamente sobre
isso: converter string em número,
onde Number, parseInt e parseFloat discordam entre si com frequência. Se
faltar base sobre os tipos, volte para
tipos de dados e typeof; o
guia de JavaScript tem a ordem inteira.
Perguntas frequentes
Devo usar null ou undefined nos meus objetos?
Por que null == undefined é true se eles são diferentes?
Qual a diferença entre isNaN e Number.isNaN?
NaN é um erro?
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 — null — developer.mozilla.org
- MDN — NaN — developer.mozilla.org
- ECMAScript — IsLooselyEqual — tc39.es


