Optional, Readonly and Why any Is a Trap
Mark properties optional or readonly, handle the missing case, and see how any switches the checks off. After this, you reach for unknown instead of any.
Course content
Sign in to save your progress.
-
Before You Write Types
-
What TypeScript Adds on Top of JavaScript
-
Install TypeScript and Run Your First .ts File
-
-
-
Types You Cannot Skip
-
Annotating Variables Without Fighting the Compiler
-
Arrays, Objects and Nested Shapes
-
Optional, Readonly and Why any Is a Trap
-
-
-
Functions That State Their Deal
-
Parameter and Return Types
-
Optional and Default Parameters Without Surprises
-
-
-
Shapes You Can Reuse
-
Interfaces vs type Aliases: Pick One and Stick With It
-
Unions, Literals and Narrowing With typeof and in
-
-
-
Compile Without the Mystery
-
tsconfig.json: target, module and strict
-
Reading tsc Errors Without Panicking
-
-
- Final Check: TypeScript for Beginners
Skip ahead: If you already use ?, readonly and know why any switches checking off, skip to "Why any is a trap", then continue.
Some properties are not always there. Some should never change after you create them. And sometimes you will be tempted to type something as any to make a red line go away. This lesson covers the first two tools, then explains why the third is the one to avoid.
Optional properties
Add ? after the name when the property may be missing.
type Product = {
title: string;
subtitle?: string;
};
const pen: Product = { title: "Pen" };
console.log(pen.subtitle.toUpperCase());
optional.ts(7,13): error TS18048: 'pen.subtitle' is possibly 'undefined'.
subtitle?: string means the property is either a string or undefined. The compiler remembers that and stops you from calling a string method on something that may not exist. Without this check, that line crashes at run time with "Cannot read properties of undefined".
You have to handle the missing case first. Two common ways:
type Product = {
title: string;
subtitle?: string;
};
const pen: Product = { title: "Pen" };
if (pen.subtitle !== undefined) {
console.log(pen.subtitle.toUpperCase());
}
console.log(pen.subtitle ?? "No subtitle");
console.log(pen.subtitle?.length);
No subtitle
undefined
The if check narrows the type inside the block. ?? supplies a default when the value is undefined or null. ?. stops and gives undefined instead of crashing. Pick the one that matches what you want to happen when the value is missing.
readonly
Mark a property readonly when it must not change after you create the object.
type Invoice = {
readonly id: number;
amount: number;
};
const invoice: Invoice = { id: 7, amount: 500 };
invoice.amount = 450;
invoice.id = 8;
const steps: readonly string[] = ["draft", "sent"];
steps.push("paid");
readonly.ts(8,9): error TS2540: Cannot assign to 'id' because it is a read-only property.
readonly.ts(11,7): error TS2339: Property 'push' does not exist on type 'readonly string[]'.
Changing amount is fine. Changing id is refused. A readonly string[] has no push, so the last line fails too. This is checked only while you compile. At run time, JavaScript will still let the property change if something slips past.
💡 Tip: Use readonly for ids and for arrays a function should only read. It is a cheap way to say "do not touch this".
Why any is a trap
any is a type that turns checking off. A value typed any can be used as a number, a string, a function or nothing at all, and the compiler says nothing.
const data: any = JSON.parse('{"count": "three"}');
const count: number = data.count;
try {
console.log(count.toFixed(1));
} catch (e) {
console.log((e as Error).message);
}
count.toFixed is not a function
There is not one error. The variable count claims to be a number, and it holds the string "three". any is not just unsafe in the place you write it. It spreads, because everything you copy out of an any value is accepted without a check.
The tempting move is to write any when an error is hard to read. Generally, that trades one visible problem for a hidden one. You do not fix the mistake. You only stop being told.
What to write instead
If you know the shape, write it, as in the earlier lessons. If you truly do not know, use unknown. It accepts anything but makes you check before you use it.
const input: unknown = "42";
console.log(input.toUpperCase());
if (typeof input === "string") {
console.log(input.toUpperCase());
}
unknown.ts(2,13): error TS18046: 'input' is of type 'unknown'.
The first call is refused. The call inside the typeof check is fine, because the compiler now knows it is a string. That is the same narrowing you will use on unions in lesson 9.
Common mistakes
- Typing a function parameter as
anyto stop an implicit-any error. Write the real type. - Using
asto claim a value has a type you have not checked. - Making every property optional "just in case". Then every use needs a check, and the shape tells the reader nothing.
If you skip this, any will creep in one quick fix at a time, and in a few weeks you will have a typed project that checks very little. Where in your own code would an any hide a bug right now?
About
Mark properties optional or readonly, handle the missing case, and see how any switches the checks off. After this, you reach for unknown instead of any.
- Duration
- 5 min
- Last update
- 10/03/2026
Comments (0)
Sign in to commentNo comments yet.