Readonly and optional properties (?) are essential when building immutable, flexible, and safe data models in TypeScript. Let’s dive deep and cover all use cases with examples.
Readonly and Optional Properties in TypeScript
When defining object shapes in TypeScript using type or interface, you can control mutability and flexibility of fields:
readonly→ property can be assigned only once, cannot be reassigned later.?(optional) → property may be missing (not required).
1. Optional Properties (?)
Optional properties make object types flexible. If a property is marked with ?, it may or may not exist.
interface User {
id: number;
name: string;
email?: string; // optional
}
const u1: User = { id: 1, name: "Alice" }; // ✅ email missing
const u2: User = { id: 2, name: "Bob", email: "bob@mail.com" }; // ✅ email present
Why use optional?
- For API responses where some fields may not exist.
- For UI props where some features are not always needed.
- For progressive object construction.
2. Readonly Properties
A readonly property cannot be reassigned after initialization.
interface Config {
readonly appName: string;
version: number;
}
const cfg: Config = { appName: "MyApp", version: 1 };
cfg.version = 2; // ✅ allowed
cfg.appName = "Other"; // ❌ Error: Cannot assign to 'appName' because it is a read-only property
Why use readonly?
- To enforce immutability (avoid accidental changes).
- For constants in configs, domain models, etc.
- For React props, which must not be mutated inside components.
3. Combining Readonly and Optional
Properties can be both readonly and optional.
interface File {
readonly id: string; // must exist, immutable
name?: string; // optional
readonly size?: number; // optional but immutable if exists
}
const file: File = { id: "abc123" };
file.name = "report.pdf"; // ✅ allowed
file.size = 500; // ✅ allowed (first assignment)
file.size = 600; // ❌ Error (readonly)
4. type vs interface with Readonly/Optional
Both type and interface support these modifiers:
type Car = {
readonly vin: string;
brand?: string;
};
interface Bike {
readonly serial: string;
model?: string;
}
5. Readonly Arrays and Tuples
TypeScript also supports readonly arrays and tuples:
const nums: readonly number[] = [1, 2, 3];
nums.push(4); // ❌ Error: Property 'push' does not exist
type Point = readonly [number, number];
const p: Point = [10, 20];
p[0] = 30; // ❌ Error: Cannot assign to index because it is a read-only property
6. Utility Types for Readonly and Optional
TypeScript provides built-in mapped types to transform properties:
Readonly<T> – make all properties readonly
interface User {
id: number;
name: string;
}
type ImmutableUser = Readonly<User>;
const user: ImmutableUser = { id: 1, name: "Alice" };
user.id = 2; // ❌ Error
Partial<T> – make all properties optional
interface User {
id: number;
name: string;
}
type PartialUser = Partial<User>;
const u: PartialUser = {}; // ✅ both optional
Combining
type ReadonlyOptionalUser = Readonly<Partial<User>>;
7. Practical Examples
(a) React Props
Props are usually readonly, and many are optional:
type ButtonProps = {
readonly label: string;
onClick?: () => void;
};
const Button = ({ label, onClick }: ButtonProps) => (
<button onClick={onClick}>{label}</button>
);
(b) API Responses in Node.js
Sometimes an API may omit fields. You can reflect that:
interface ApiResponse {
readonly id: number;
data?: string;
}
const res: ApiResponse = { id: 101 }; // ✅
res.id = 102; // ❌ readonly
(c) Angular Component Input
Angular’s @Input props are essentially readonly, but often optional:
interface UserProfile {
readonly id: string;
name?: string;
}
@Component({ ... })
export class UserComponent {
@Input() user!: UserProfile;
}
8. Comparison Table
| Modifier | Meaning | Can reassign? | Can omit? |
|---|---|---|---|
| Normal | Required, mutable | ✅ Yes | ❌ No |
? | Optional, mutable if exists | ✅ Yes | ✅ Yes |
readonly | Required, immutable | ❌ No | ❌ No |
readonly ? | Optional, immutable if exists | ❌ No | ✅ Yes |
9. Best Practices
- Use
readonlyfor immutable fields like IDs, config constants, or React props. - Use
?for fields that may not exist (API data, UI props). - Use both when optional data should not be changed after being set.
- Prefer utility types (
Readonly,Partial) when transforming whole object types.
✅ Conclusion:readonly and ? make TypeScript expressive and safe. They let you describe real-world data models accurately — where some fields never change, and others may not even exist. Combined with utility types, they give you precise control over object structures.