Build

TypeScript Optional vs. Nullable: Model API Fields Correctly

A missing property, a null value, and an undefined read are different states.

Published
Read time3 min read
A JSON object with an explicit null value branches into two type-model panels, distinguishing a present value from an absent property.

In TypeScript, an optional property and a nullable property describe different API states. name?: string means the property may be absent; name: string | null means the property is required but its value may be null. Use name?: string | null only when both conditions are allowed by the contract.

This distinction matters when you turn JSON into types: one sample can show a value of null, but it cannot prove whether the property may be omitted in other responses. If you have a sample ready, the Letinix JSON to TypeScript Converter can generate a starting declaration; review the result against the actual API contract.

The three shapes#

Consider these illustrative API response shapes:

present.jsonJSON
{ "displayName": "Mina" }
null.jsonJSON
{ "displayName": null }
missing.jsonJSON
{}

They are not interchangeable. The first contains a string value. The second contains the property with a JSON null value. The third does not contain the property at all.

Here are the corresponding TypeScript models:

user.tsTypeScript
interface UserWithName {
  displayName: string;
}

interface UserWithNullableName {
  displayName: string | null;
}

interface UserWithOptionalName {
  displayName?: string;
}

interface UserWithOptionalNullableName {
  displayName?: string | null;
}

Choose the narrowest model that matches the documented contract. Making every field optional may quiet compiler errors, but it also weakens the guarantee your type provides.

What ? means#

The ? modifier makes a property optional. An object typed with displayName?: string may omit that key:

usage.tsTypeScript
const a: UserWithOptionalName = {};
const b: UserWithOptionalName = { displayName: "Mina" };

When you read the property, TypeScript must account for the possibility that it is absent:

usage.tsTypeScript
declare const user: UserWithOptionalName;

const name = user.displayName;
// string | undefined when strictNullChecks is enabled

That undefined is a JavaScript read result; JSON itself has no undefined value. A missing JSON key becomes an undefined result when you access it as an object property.

With exactOptionalPropertyTypes, TypeScript also distinguishes an omitted optional property from explicitly assigning undefined unless undefined is included in the declared value type. This option is useful when the presence of a property has meaning in your application.

What | null means#

A union with null allows a present property to hold a null value:

profile.tsTypeScript
interface UserProfile {
  displayName: string | null;
}

const profile: UserProfile = { displayName: null };

The property is still required. This object does not satisfy UserProfile because the key is missing:

profile.tsTypeScript
const profile: UserProfile = {};
// Type error: displayName is required

Use T | null when the API contract explicitly uses null to represent a known state, such as no assigned owner or no value available. Do not assume that a null in one example means the property can also be absent.

When both states are allowed#

Some APIs allow the field to be omitted and also return null when it is present but has no value. If the API documentation or observed contract supports both cases, model them explicitly:

profile.tsTypeScript
interface UserProfile {
  displayName?: string | null;
}

Consumers must then handle all three outcomes: a string, null, or undefined after reading an omitted property.

labels.tsTypeScript
function labelFor(user: UserProfile): string {
  if (user.displayName == null) {
    return "Unnamed";
  }

  return user.displayName;
}

The == null check intentionally matches both null and undefined. If your logic needs to distinguish an absent key from an explicitly null key, test property presence separately with Object.hasOwn(user, "displayName") and then handle the value.

How to infer the right type from JSON#

A converter can infer types from the values in the sample. It cannot infer every possible response state from a single object. A property that appears with a string in your sample may be absent in another response; a property that is null may be nullable, or the sample may simply not contain a non-null example.

  • Check the API's official schema or response documentation for required properties and nullability.
  • Compare multiple realistic responses, including empty, partial, and error responses where relevant.
  • Keep optionality and nullability separate in the type declaration.
  • Add a runtime check when the incoming payload is not guaranteed to match the documented contract.

The Letinix converter documents optional-key inference when objects in the same array have different keys, and it maps a field that is null in some array objects and has a value in others to a union with null. Those are useful inferences from the supplied sample; they do not replace the API specification.

Common modeling mistakes#

  • Using ? to mean nullable. field?: string does not permit a present null value.
  • Using | null to mean optional. field: string | null still requires the property to exist.
  • Making every property optional. This can hide contract mismatches and make consumers handle states the API never permits.
  • Trusting one sample as a complete schema. Use authoritative documentation or more representative responses.
  • Assuming the type validates incoming JSON. Type declarations do not validate network data at runtime.

References#

TypeScript Handbook: Object Types

TypeScript 4.4 release notes: exact optional property types

Letinix JSON to TypeScript Converter

Muhammad Azlaan Zubair

Software Engineer and Researcher, writing about AI, developer tooling and automation.

Newsletter

New guides, comparisons and analysis, sent only when there is something worth your time.

  • New articles worth your time, not every link we publish.
  • Tools and resources when they launch.
  • No spam, no selling your address. Unsubscribe anytime.

Get useful signal by email

We're building the list before the first issue. You'll get an email when the newsletter starts.