Every codebase has that one object. You built it eight months ago, it flows through eleven
functions, and the only documentation for its shape is vibes and a console.log you deleted in
2024. Last week you taught TypeScript to check the values you hand a function. This week you
teach it to describe the objects those values live inside, so neither of you has to remember the
shape from memory again.
Describing a shape instead of guessing at one
In plain JS, an object's shape lives in your head, or in whatever function happened to build it
last. TypeScript gives that shape a name with an interface: a list of properties, each with its
own type, that any object claiming to be that shape has to match.
The ES6 you write
const player = {
name: 'Cliff',
twitterHandle: '@cliffhogg',
seasonNumber: 21,
}The TS version
interface Player {
name: string
twitterHandle?: string
readonly seasonNumber: number
}
const player: Player = {
name: 'Cliff',
seasonNumber: 21,
}What changed
The JS object on the left has a shape, you just cannot ask anything to check it. The TS version names that shape with interface Player, and the const player: Player line tells the compiler which shape this particular object is supposed to satisfy. Try to typo seasonNumber as season and TypeScript catches it before you save.
Notice player above skips twitterHandle entirely and still compiles. That is the next piece.
Optional properties: the ?
A ? right after a property name means "this property is allowed to be missing." Not null,
not empty string, just plain absent from the object. If you have written PHP 7 nullable type
hints like ?string $handle = null, your instinct is close but not identical: PHP's ? says the
value can be null, TypeScript's ? on an interface property says the key itself can be left
out of the object altogether. A player without a twitterHandle property is valid. A player with
twitterHandle: null is a different claim, and null is not what the ? promises here.
readonly: what it does, and what it does not
readonly on a property means the compiler will stop you from writing to it after the object is
created. player.seasonNumber = 22 is a compile error, full stop, right there in your editor.
What it will not do is freeze the object at runtime. Compile the code above and ship it, and
player.seasonNumber = 22 runs just fine in plain JavaScript, because readonly is erased along
with every other type, exactly like last week. If you need an actual runtime-enforced freeze, that
is what Object.freeze is for, a completely different tool solving a completely different
problem. readonly protects you at the keyboard, not in production.
That is the whole lesson. Now prove it in the comp below.