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

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.

Rodolfo Mori6 min de leitura

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.

js
const pedido = { id: 4821, cupom: null };

console.log(pedido.cupom);
console.log(pedido.observacao);
console.log(Number('dois'));
null undefined NaN

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:

js
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'));
undefined undefined undefined undefined

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:

js
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));
{"id":4821,"cupom":null,"entregaEm":null} {"id":4821} [ 'id', 'cupom', 'entregaEm' ]

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

js
console.log(null == undefined);
console.log(null === undefined);
console.log(null == 0);
console.log(undefined == 0);
console.log(null >= 0);
true false false false true

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í:

js
const pedido = { id: 4821, cupom: null };

console.log(pedido.cupom == null);
console.log(pedido.observacao == null);
console.log(pedido.id == null);
true true false

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:

js
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 false -1 true true

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

js
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));
true false false false true

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?” 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.

js
const pedido = { id: 4821, cliente: 'Ana Souza' };

console.log(pedido.itens.length);
file:///private/tmp/loja/l6-length.mjs:3 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, '' e NaN também são falsos. Uma quantidade zero legítima some da tela por causa disso.
  • Comparar com NaN usando ===. Nunca dá true. Use Number.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:

js
const pedido = { id: 4821, cupom: null };

console.log(pedido.itens?.length);
console.log(pedido.itens?.length ?? 0);
console.log(pedido.cupom ?? 'sem cupom');
undefined 0 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 ||:

js
const frete = 0;

console.log(frete ?? 24.9);
console.log(frete || 24.9);
0 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:

js
function totalSeguro(valor) {
  const numero = Number(valor);
  return Number.isNaN(numero) ? 0 : numero;
}

console.log(totalSeguro('289.90'), totalSeguro('R$ 289,90'), totalSeguro(null));
289.9 0 0

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.

  • javascript
  • null
  • undefined
  • nan
  • comparacao

Perguntas frequentes

Devo usar null ou undefined nos meus objetos?
Use null quando a ausência é uma decisão sua — pedido sem cupom, entrega sem data. Deixe undefined para o que a linguagem produz sozinha. A regra importa porque JSON.stringify preserva null e apaga undefined.
Por que null == undefined é true se eles são diferentes?
A especificação define uma exceção explícita: na comparação frouxa, null e undefined só são iguais entre si e a mais nada. É a única razão de x == null ser um teste legítimo para "vazio de qualquer um dos dois tipos".
Qual a diferença entre isNaN e Number.isNaN?
O isNaN global converte o argumento para número antes de testar, então responde true para qualquer texto não numérico. Number.isNaN não converte nada: só responde true se o valor já for o próprio NaN.
NaN é um erro?
Não, é um valor válido do tipo number, e nenhuma exceção é lançada quando ele aparece. Esse é o problema: ele atravessa o programa em silêncio e só quebra várias linhas depois, longe da conta que o originou.

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 — null — developer.mozilla.org
  2. MDN — NaN — developer.mozilla.org
  3. ECMAScript — IsLooselyEqual — tc39.es

Continue por aqui