`?:` for "Maybe Set", `readonly` for "Never Reassign"
Optional & Readonly
`?:` makes a property optional; `readonly` makes it immutable. Two of the most useful modifiers.
What you'll learn
- Mark properties optional
- Mark properties readonly
- Combine both
Two modifiers you’ll use constantly on object types.
Optional — ?:
type User = {
name: string;
age?: number; // optional
};
const a: User = { name: "Ada" }; // ✓ age omitted
const b: User = { name: "Grace", age: 86 }; // ✓ age?: number is shorthand for age: number | undefined —
slightly different (we’ll get to that).
Accessing Optional Properties
The accessed property has type T | undefined:
function describe(user: User) {
if (user.age !== undefined) {
user.age.toFixed(2); // ✓ narrowed to number
}
} Forgetting to check is a common error TS catches.
?: undefined vs ?:
type A = { age?: number };
type B = { age: number | undefined };
const a: A = {}; // ✓
const b: B = {}; // ✗ Property 'age' is missing
const b: B = { age: undefined }; // ✓ ?: allows the property to be omitted. : T | undefined requires
the property to exist, with undefined an allowed value.
In practice ?: is what you usually want.
Readonly — Can’t Reassign
type Point = {
readonly x: number;
readonly y: number;
};
const p: Point = { x: 0, y: 0 };
p.x = 5;
// ~ Cannot assign to 'x' because it is a read-only property. readonly prevents direct reassignment. The value of p.x is
still mutable in JS at runtime — readonly is a type-system
guarantee only.
Readonly<T> Helper
type User = { name: string; age: number };
type FrozenUser = Readonly<User>;
// equivalent to: { readonly name: string; readonly age: number } Readonly<T> is a built-in utility type that flips every property
to readonly.
Use Them Together
type Config = {
readonly apiUrl: string;
readonly timeout?: number;
}; Mark the API surface — readonly for things that won’t change,
?: for optional configuration.
Up Next
The special types — void, never, unknown, any, null,
undefined.