Optional and Readonly Properties
Not every property is always present, and not every property should be writable after creation. TypeScript gives you two modifiers for this: ? for optional properties and readonly for immutable ones.
Optional properties
Section titled “Optional properties”Adding ? to a property name declares it as optional — the property may be present or absent:
interface SearchOptions { amount: number; difficulty?: Difficulty; category?: number;}An object satisfies SearchOptions whether or not difficulty and category are present:
const basic: SearchOptions = { amount: 10 }; // ✓const specific: SearchOptions = { amount: 10, difficulty: 'hard' }; // ✓const full: SearchOptions = { amount: 10, difficulty: 'easy', category: 9 }; // ✓Using optional properties safely
Section titled “Using optional properties safely”When a property is optional, TypeScript widens its type to T | undefined. You must check before using it:
function buildUrl(options: SearchOptions): string { let url = `https://opentdb.com/api.php?amount=${options.amount}&type=multiple`;
if (options.difficulty) { url += `&difficulty=${options.difficulty}`; // safe — checked first }
return url;}Without the if guard, TypeScript would report an error: you cannot use options.difficulty directly because it might be undefined.
Readonly properties
Section titled “Readonly properties”The readonly modifier prevents a property from being reassigned after the object is created:
interface TriviaQuestion { readonly question: string; readonly category: string; readonly difficulty: Difficulty; readonly correctAnswer: string; allAnswers: string[];}question, category, difficulty, and correctAnswer are fixed at creation — they describe the question as it came from the API and should not change. allAnswers is mutable because the array is shuffled during normalization.
Attempting to reassign a readonly property is a compile error:
const q: TriviaQuestion = { /* ... */ };q.correctAnswer = 'Mars'; // Error: Cannot assign to 'correctAnswer' because it is a read-only propertyreadonly arrays
Section titled “readonly arrays”You can mark an array property as readonly to prevent mutation of the array itself:
interface TriviaQuestion { readonly allAnswers: readonly string[];}readonly string[] (or equivalently ReadonlyArray<string>) prevents push, pop, splice, and direct index assignment. The answers can be read but not modified after the question is created.
readonly vs const
Section titled “readonly vs const”const prevents a variable from being reassigned. readonly prevents a property from being reassigned. They are different constraints:
const q = { correctAnswer: 'Mercury' };q.correctAnswer = 'Mars'; // ✓ — const prevents reassigning q, not its properties
interface Fixed { readonly correctAnswer: string; }const q2: Fixed = { correctAnswer: 'Mercury' };q2.correctAnswer = 'Mars'; // Error — readonly prevents changing the propertyUse const for variables, readonly for properties.
Optional vs readonly together
Section titled “Optional vs readonly together”Both modifiers can appear on the same property:
interface HighScore { score: number; total: number; date: string; readonly id?: string; // optional and immutable if present}AceIt’s TriviaQuestion
Section titled “AceIt’s TriviaQuestion”In AceIt’s types.ts, TriviaQuestion uses readonly on the fields that come directly from the API. Once a question is normalized, its text, category, difficulty, and correct answer are facts — they should not change:
interface TriviaQuestion { readonly question: string; readonly category: string; readonly difficulty: Difficulty; readonly correctAnswer: string; readonly allAnswers: string[];}This communicates intent: TriviaQuestion is a value object, not a mutable record. If any code tried to “fix” a wrong answer in place, the compiler would stop it.
Exercise
Section titled “Exercise”Update the interfaces in types.ts:
- Add
readonlytoquestion,category,difficulty,correctAnswer, andallAnswersinTriviaQuestion. - Add
readonlyto all properties inHighScore— once a score is recorded, none of its fields should change. - Create an interface
FetchOptionswithamount: number(required), anddifficulty?: Difficultyandcategory?: number(both optional). - Write a function
buildUrl(options: FetchOptions): stringthat constructs the Open Trivia DB URL, appending optional parameters only when present.
?marks a property as optional — it may be present or absent. TypeScript widens its type toT | undefinedand requires a check before use.readonlyprevents a property from being reassigned after creation.readonlyon an array property does not prevent mutation of the array — usereadonly T[]orReadonlyArray<T>to prevent that too.constprevents variable reassignment;readonlyprevents property reassignment — they are different constraints.- Both
?andreadonlycan appear on the same property.