1. Introduction
Real-world objects often have properties that aren't always present, or that should never change after creation. TypeScript provides two modifiers for interface (and type alias) members to express this: ? marks a property as optional, and readonly marks a property as unassignable after its initial value is set. Both are enforced only by the type checker, not at runtime.
Cricket analogy: A scorecard has a mandatory 'runs' field but an optional 'notOut' flag that only appears if the batter wasn't dismissed, while the 'matchDate' field is set once and never rewritten after the toss.
2. Syntax
interface Product {
readonly sku: string; // cannot be reassigned after creation
name: string;
discount?: number; // may be omitted entirely
}
const item: Product = { sku: "A100", name: "Widget" };
// item.sku = "B200"; // Error: Cannot assign to 'sku' because it is read-only.
item.name = "Updated Widget"; // OK, not readonly3. Explanation
An optional property (propertyName?: Type) means the property may be omitted from an object entirely. Its resolved type inside the interface becomes Type | undefined, so any code reading it must handle the possibility that the value is undefined before using it (often with optional chaining ?. or a guard).
Cricket analogy: If a player object has manOfTheMatch?: string, the field is string | undefined, so before printing the award you must check it exists, since a match without a standout performer leaves it unset.
A readonly property can be set once — typically when the object literal is created — but cannot be reassigned afterward. This is purely a compile-time check performed by the TypeScript compiler; it does not freeze the object at runtime. Nothing stops plain JavaScript, or TypeScript code using a type assertion, from mutating a 'readonly' property. readonly is also shallow: if a readonly property holds an object or array, that nested object's own properties remain fully mutable unless they too are marked readonly.
Cricket analogy: A readonly captainName is set once when the squad list is announced, but plain JavaScript or a type assertion could still overwrite it mid-series; and if the value is a nested homeGround object, its pitchType field stays mutable unless separately marked readonly.
Gotcha: readonly is not Object.freeze(). It only prevents *reassignment* through the TypeScript type system at compile time. At runtime, the property is an ordinary mutable JavaScript property, and a type assertion like (item as { sku: string }).sku = 'X' bypasses the check entirely. For genuine deep immutability at runtime you need Object.freeze() (shallow) or a dedicated immutability library.
4. Example
interface Config {
readonly apiKey: string;
timeout?: number;
}
function connect(config: Config): void {
const effectiveTimeout = config.timeout ?? 5000;
console.log(`Connecting with key ${config.apiKey}, timeout ${effectiveTimeout}ms`);
}
const config: Config = { apiKey: "secret-123" };
connect(config);
// readonly is compile-time only: an assertion can bypass it.
(config as { apiKey: string }).apiKey = "secret-999";
console.log(config.apiKey);5. Output
Connecting with key secret-123, timeout 5000ms
secret-9996. Key Takeaways
propertyName?: Typemarks a property as optional; its effective type isType | undefined.readonly propertyName: Typeprevents reassignment after initial creation, but only at compile time.- readonly does not deep-freeze objects — nested properties remain mutable, and type assertions can bypass the check.
- Optional properties require handling
undefined, often via optional chaining?.or nullish coalescing??. - For true runtime immutability, combine readonly with
Object.freeze()or an immutability library.
Practice what you learned
1. What is the effective type of a property declared as `age?: number` inside an interface?
2. Does marking a property 'readonly' prevent it from being mutated at runtime in plain JavaScript?
3. If `readonly settings: { theme: string }` is a property on an interface, what does readonly protect?
4. Which technique correctly handles reading an optional property `discount?: number` before using it in a calculation?
5. What compiles-away trick can bypass a 'readonly' restriction, proving it is not true runtime immutability?
Was this page helpful?
You May Also Like
Interfaces in TypeScript
Learn how TypeScript interfaces describe the shape of objects using structural typing, and why they are the primary tool for contract-based design.
Readonly Properties in TypeScript
Use the readonly modifier to prevent a class property from being reassigned after it is initially set in the constructor or at declaration.
Extending Interfaces in TypeScript
Learn how the `extends` keyword lets one interface inherit and build upon the members of one or more other interfaces.
Union and Intersection Types in TypeScript
Combine types with `|` (union) and `&` (intersection) to model values that can be one of several shapes or must satisfy all of them.
any, unknown, never and void in TypeScript
The four special types that sit outside normal type checking — the unsafe escape hatch, the safe escape hatch, the impossible type, and the absent-return type.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics