What is exact optional property types?
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.
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?: Tnow means that the property can either be present with a value of typeT, or it can be entirely absent from the object. It *cannot* explicitly be assignedundefined. - If you truly want a property that *must* be present but can hold the value
undefined, you must explicitly declare it asp: T | undefined.
How it Works (with `exactOptionalPropertyTypes: true`)
Consider the following interface with exactOptionalPropertyTypes enabled:
// 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 aUserSettingsobject withouttheme, or withtheme: 'dark'ortheme: 'light'. Assigningtheme: undefinedwill result in a type error.language: string | undefined: You *must* include thelanguageproperty when creating aUserSettingsobject. Its value can be astring(e.g.,'en-US') orundefined.
Example
// 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
undefinedwhen 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).