O problema: acessar propriedades aninhadas é frágil
Acessar propriedades aninhadas em um objeto funciona super bem — até o momento em que um dos níveis simplesmente não existe:
user.address é undefined, e tentar ler .city a partir de undefined lança TypeError: Cannot read properties of undefined. Dados do mundo real — respostas de API, JSON parseado, consultas ao DOM — estão cheios de campos que podem ou não existir, e ficar escrevendo verificações defensivas para cada nível vira uma bagunça rapidinho:
const city = user && user.address && user.address.city;
Dá pra ler com dois níveis. Vira um pesadelo com quatro. O operador ?. é a solução mais limpa.
?. faz curto-circuito em null e undefined
O optional chaining acessa uma propriedade apenas se o valor antes dele não for null nem undefined. Quando for, a expressão inteira devolve undefined e a avaliação para por aí.
Na primeira linha, name é lido normalmente. Na segunda, ao chegar em user.address, o valor é undefined e a cadeia é interrompida ali — o resto nem chega a ser executado. A terceira linha daria erro sem o ?., porque .toUpperCase() seria chamado em undefined; com o ?., a expressão inteira simplesmente resolve para undefined, sem estourar.
O modelo mental é esse: ?. significa "se o que vem antes de mim for null ou undefined, desiste e devolve undefined — caso contrário, segue em frente."
Funciona também em arrays e chamadas de função
São três sintaxes para a mesma ideia:
?.[...]para acessar arrays ou propriedades dinâmicas.?.()para chamar algo que pode ou não ser uma função.?.namepara acessar propriedades comuns.
Os três fazem short-circuit da mesma forma. user?.notAMethod?.() não quebra mesmo quando notAMethod não existe — o segundo ?. encontra undefined e para por ali.
Isso cai como uma luva para callbacks opcionais:
Chega de ficar colocando if (typeof onDone === "function") antes de cada chamada.
Só null e undefined disparam o curto-circuito
Esse é o detalhe que pega muita gente desprevenida. O ?. só se importa com valores nullish. Outros valores falsy — 0, "", false, NaN — são alvos perfeitamente válidos para continuar a cadeia (na medida em que o autoboxing permite, claro):
data.count vale 0, que é falsy mas não é nullish. Por isso o ?.toFixed(2) é executado normalmente e retorna "0.00". Compare com o comportamento do &&:
A versão com && devolve 0 porque data.count é falsy e faz curto-circuito. Já a versão com ?. devolve "0.00", já que 0 não é nullish. Se o seu objetivo é parar também no 0, use &&. Agora, se você quer parar só quando "o valor não existe", o ?. é o certo.
A posição do ? faz diferença
O ?. protege o valor que vem antes dele — não o que vem depois. Por isso, coloque-o no nível que pode estar ausente:
Todos os três funcionam aqui porque, na verdade, não falta nada. Mas se config.server pudesse ser undefined, você precisaria de config.server?.host — colocar ?. antes de server não ajudaria, já que o problema é tentar ler .host de um server inexistente.
Uma boa regra prática: coloque ?. em cada nível onde o valor anterior pode realmente ser nulo ou indefinido. Espalhar ?. em todo ponto "por via das dúvidas" acaba escondendo bugs e deixando o código mais poluído.
Atribuição com ?. não funciona
O optional chaining é só para leitura. Você não pode usá-lo no lado esquerdo de uma atribuição:
user?.address?.city = "Paris"; // SyntaxError
Faz sentido se você parar pra pensar — o que significaria atribuir um valor a uma propriedade de undefined? Se a ideia é atribuir só quando o objeto pai existe, escreva assim:
Quando usar (e quando não usar)
O ?. brilha quando um valor é legitimamente opcional:
- Respostas de API em que certos campos podem ou não vir.
- Consultas ao DOM:
document.querySelector(".banner")?.remove(). - Callbacks que podem não ter sido passados:
options.onError?.(err). - Chamadas encadeadas em libs onde resultados intermediários podem ser
null.
Agora, é a ferramenta errada quando o valor sempre deveria existir. Sair espalhando ?. só pra calar erros nesses casos transforma um bug barulhento e fácil de achar ("TypeError na linha 42") num bug silencioso — uma variável que aparece misteriosamente como undefined três funções adiante. Quando algo tem que estar lá, deixe estourar: o stack trace está te fazendo um favor.
Um exemplo realista
Extraindo um valor de uma resposta de API que pode vir incompleta:
Cada ?. cuida de um nível de incerteza. O avatarUrl acaba virando undefined sem drama. Já o onClick?.() só chama o handler se ele estiver lá de fato.
Repare no ?? da linha do displayName — essa é a outra metade do padrão. O ?. devolve undefined quando algo está faltando; já o ?? permite colocar um valor padrão sem tropeçar em valores falsy legítimos como 0 ou "".
Próximo passo: nullish coalescing
?. e ?? são a mesma ideia aplicada a problemas diferentes: os dois tratam null e undefined como "ausente" e deixam os demais valores falsy em paz. No próximo tópico, vamos ver como o ?? entrega valores padrão sensatos — e por que o || vem fazendo isso errado em silêncio há anos.
Perguntas frequentes
O que faz o operador ?. no JavaScript?
O operador ?. acessa uma propriedade, índice de array ou método somente se o valor antes dele não for null nem undefined. Quando é, a expressão inteira é curto-circuitada e retorna undefined em vez de lançar um erro. Assim, user?.address?.city não quebra mesmo se user ou address não existirem.
Quando usar optional chaining no JavaScript?
Use ?. quando o valor realmente pode não existir — respostas de API com campos opcionais, buscas no DOM que talvez não encontrem o elemento, callbacks que podem ou não ser passados. Não use para esconder bugs em lugares onde o valor deveria sempre existir: nesses casos, um valor ausente é uma informação importante que você quer ver explodindo na sua cara.
Qual a diferença entre ?. e && no JavaScript?
Na maioria dos casos o resultado é igual, mas o ?. só faz curto-circuito em null ou undefined, enquanto o && faz em qualquer valor falsy — incluindo 0, '' e false. Por exemplo, obj && obj.count retorna 0 quando count é 0; já obj?.count também retorna 0, mas a cadeia só é interrompida nos valores nullish, o que costuma ser o comportamento que você quer.
Dá para usar optional chaining com arrays e chamadas de função?
Sim. arr?.[0] lê com segurança um índice de array, e fn?.() executa a função apenas se ela existir. A regra é a mesma nos dois casos: se o valor antes do ?. for null ou undefined, a expressão toda vira undefined e nada mais é executado.