Optional and Readonly Properties
Marking object properties as optional with ? and locking them with readonly.
2 min read
Beyond just naming a property's type, TypeScript lets you describe two more things about it directly in an interface or type: whether it has to be there at all, and whether it can change after the object is created.
Optional properties
A ? after a property name marks it as optional — an object satisfying the type doesn't need to include it:
interface User {
name: string;
age: number;
email?: string; // optional
}
const userA: User = { name: "Ada", age: 36 }; // fine, no email
const userB: User = { name: "Grace", age: 45, email: "g@example.com" }; // also fineAn optional property's type is really T | undefined under the hood — accessing it without checking first can produce undefined at runtime, so TypeScript requires narrowing (covered in a later lesson) before you use it in a way that assumes it's present:
function printEmail(user: User) {
console.log(user.email.toUpperCase()); // Object is possibly 'undefined'
}
function printEmail(user: User) {
if (user.email) {
console.log(user.email.toUpperCase()); // fine -- narrowed to string
}
}readonly properties
readonly allows reading a property but not reassigning it after the object is created:
interface Point {
readonly x: number;
readonly y: number;
}
const point: Point = { x: 10, y: 20 };
point.x = 99; // Cannot assign to 'x' because it is a read-only propertyThis is a compile-time check only — like const, it doesn't freeze the object at runtime (Object.freeze() is the runtime equivalent, and does something related but distinct); readonly just stops your own TypeScript code from writing to that property.
readonly arrays
function printAll(items: readonly string[]) {
items.push("new item"); // Property 'push' does not exist on type 'readonly string[]'
}readonly string[] (or the equivalent ReadonlyArray<string>) removes every mutating method — push, pop, splice, sort — from the type, leaving only methods that read without changing the array. This is a useful signal on a function parameter: it tells callers, and TypeScript, that the function won't modify the array they passed in.
Combining both
interface Config {
readonly apiUrl: string;
timeout?: number;
}apiUrl must always be provided and can never be reassigned once set; timeout may be omitted entirely. Together, ? and readonly let an interface describe not just a shape, but the actual rules around how that shape is meant to be used — which is a large part of what makes TypeScript's types more expressive than a runtime shape check could easily replicate.
The next section moves into more advanced type constructs, starting with combining types using unions and intersections.