🔷 TypeScript Q70 / 101

What is exact optional property types?

AI-Powered Answer ✓ Answered

Exact Optional Property Types, introduced in TypeScript 4.4, is a compiler option (`exactOptionalPropertyTypes` in `tsconfig.json`) designed to clarify the meaning of optional properties in interfaces and object types. It helps distinguish between a property that is truly absent and a property explicitly set to `undefined`, thereby enhancing type safety and reducing potential runtime errors.

The Problem Before TypeScript 4.4

Prior to TypeScript 4.4, an optional property p?: T was broadly interpreted as p: T | undefined. This meant that if you declared an optional property, TypeScript would allow it to be either of type T, or undefined, or entirely absent from the object. While convenient, this conflation could lead to ambiguities, especially when interacting with external APIs or JSON data where undefined might carry a different semantic meaning than an omitted property.

typescript
interface LegacyConfig {
  timeout?: number; // Equivalent to timeout: number | undefined
}

const config1: LegacyConfig = {}; // OK: timeout is absent
const config2: LegacyConfig = { timeout: 1000 }; // OK: timeout is number
const config3: LegacyConfig = { timeout: undefined }; // OK: timeout is undefined

The issue arose because these three states (absent, T, undefined) were treated as interchangeable for an optional property, despite some use cases requiring a strict distinction between 'not provided' and 'provided with an undefined value'.

Introducing Exact Optional Property Types

With the exactOptionalPropertyTypes compiler option enabled (set to true in tsconfig.json), TypeScript introduces a stricter interpretation for optional properties (p?: T):

  • An optional property p?: T now means that the property can either be present with a value of type T, or it can be entirely absent from the object. It *cannot* explicitly be assigned undefined.
  • If you truly want a property that *must* be present but can hold the value undefined, you must explicitly declare it as p: T | undefined.

How it Works (with `exactOptionalPropertyTypes: true`)

Consider the following interface with exactOptionalPropertyTypes enabled:

typescript
// tsconfig.json:
// {
//   "compilerOptions": {
//     "exactOptionalPropertyTypes": true
//   }
// }

interface UserSettings {
  theme?: 'dark' | 'light'; // Can be 'dark', 'light', or absent
  language: string | undefined; // Must be present, can be string or undefined
}
  • theme?: 'dark' | 'light': You can create a UserSettings object without theme, or with theme: 'dark' or theme: 'light'. Assigning theme: undefined will result in a type error.
  • language: string | undefined: You *must* include the language property when creating a UserSettings object. Its value can be a string (e.g., 'en-US') or undefined.

Example

typescript
// Assuming tsconfig.json has "exactOptionalPropertyTypes": true

interface Product {
  id: number;
  name: string;
  description?: string; // Can be string or absent, NOT undefined
  imageUrl: string | undefined; // Must be present, can be string or undefined
}

// --- Valid Assignments ---

const product1: Product = {
  id: 1,
  name: "Laptop",
  // description is absent, which is valid
  imageUrl: "https://example.com/laptop.jpg" // imageUrl is a string
};

const product2: Product = {
  id: 2,
  name: "Mouse",
  description: "Ergonomic wireless mouse", // description is a string
  imageUrl: undefined // imageUrl is explicitly undefined
};

const product3: Product = {
  id: 3,
  name: "Keyboard",
  description: "Mechanical keyboard with RGB",
  imageUrl: "https://example.com/keyboard.jpg"
};

// --- Invalid Assignments (Type Errors) ---

// Error: Type 'undefined' is not assignable to type 'string | undefined'.
//   Type 'undefined' is not assignable to type 'string'.
//   'description' is an exact optional property. Consider adding 'undefined' to its type if you intend for it to be explicitly 'undefined'.
const product4: Product = {
  id: 4,
  name: "Webcam",
  description: undefined, // ERROR: Cannot assign undefined to 'description' when exactOptionalPropertyTypes is true
  imageUrl: "https://example.com/webcam.jpg"
};

// Error: Property 'imageUrl' is missing in type '{ id: number; name: string; }' but required in type 'Product'.
const product5: Product = {
  id: 5,
  name: "Monitor"
  // ERROR: imageUrl is missing, but it's not optional (it's string | undefined, meaning it must be present)
};

Benefits

  • Increased Type Safety: Prevents developers from mistakenly assigning undefined when the intent was for the property to be omitted.
  • Clearer Intent: Makes the type definition more explicit about whether a property can truly be missing or if it must always be present, even if its value is undefined.
  • Better API Modeling: Helps accurately model APIs, especially those that differentiate between a field being absent (e.g., for default values) and a field being explicitly null/undefined (e.g., to clear a value).