Topic 1.5
Object Types
In one line
An object type lists the properties a value must have and the type of each: { name: string; age: number }. Properties can be optional (?) or read-only, and a fresh object literal may not carry unlisted extras.
Think of it like this
A form with labelled boxes: Name, Age, Email (optional). A clerk can tell at a glance if a form is missing the name, if someone wrote letters in the Age box, or if there is a stray box that doesn't belong. An object type is that form. It lists the boxes, says what goes in each, and says which can stay empty.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Object
- A bundle of named values, written
{ name: "Asha", age: 30 }. - Property
- One named value in an object.
nameis a property of the object above. - Object type
- A description of an object's shape:
{ name: string; age: number }. - Optional property
- A property marked with
?that is allowed to be missing. - readonly property
- A property that code can read but not assign to.
- Object literal
- An object written out directly in the code with
{ ... }. - Fresh literal
- An object literal that has just been written in place and not yet stored in a variable. Only these get the excess property check.
- Excess property
- A property in an object literal that the target type doesn't list.
- exactOptionalPropertyTypes
- A flag that treats "missing" and "present but undefined" as different things.
Step by step
01Write the shape
Put the property names and their types between braces. Both ; and , work as separators.
The same shape works in a parameter, which is where object types appear most often: the function says exactly what it needs.
const user: { name: string; age: number } = {
name: "Asha",
age: 30,
};
function show(p: { x: number; y: number }) {
return p.x + "," + p.y;
}02Optional with ?
Add ? after the name. Now email may be left out, and reading it gives string | undefined.
That | undefined is TypeScript reminding you the value might not be there. The next two topics show how to handle that.
const user: { name: string; age: number; email?: string } = {
name: "Asha",
age: 30,
};
const mail = user.email; // string | undefined03readonly: set once, read forever
Mark a property readonly and assignments to it are refused. Other properties stay changeable.
The compiler is the only guard. At runtime the property can still change, so don't use readonly for security.
04Excess properties: typos in object literals
A fresh object literal is checked for extra properties. This catches colour written where color was meant.
A variable that holds an object with extras is accepted: the value still has every property the type asks for.
05Missing is not the same as undefined
With exactOptionalPropertyTypes, nickname?: string accepts a missing property or a string. Writing nickname: undefined is an error.
This matches how JavaScript behaves: "nickname" in obj and Object.keys can tell the difference. The flag is strict, and some libraries need nickname?: string | undefined.
Try it yourself
- 1
Remove a required property
In the first example delete
age: 30,. Predict the error before you look: the message names the missing property (TS2741: Property 'age' is missing in type '{ name: string; }' but required in type ...). - 2
Typo the key
In the second example change
y: 2in line 2 toyy: 2. There are two problems (a missingyand an unknownyy) but the checker reports one:TS2353onyy. Fix it, and then the missingyappears. Errors often come one layer at a time. - 3
Allow undefined on purpose
In the third example change line 1 to
nickname?: string | undefined. The error on line 3 disappears. Which behaviour do you want in your own code?
Code & diagrams
Changing label is allowed, assigning id is not. But look at the console: the id really became 2, because readonly vanishes when the code is turned into JavaScript.
Console
(nothing was printed)Expected console output
Asha undefined
{ id: 2, label: 'B' }The first line is rejected, but the second route (through the variable q) is accepted. The type alias on line 1 just names the shape (Topic 3.1).
Console
(nothing was printed)Expected console output
{ x: 1, y: 2, z: 3 } { x: 1, y: 2, z: 3 }This flag is on in every example here because tsc --init turns it on. In a project without it, line 3 would be fine. Change line 1 to nickname?: string | undefined and the error goes away.
Console
(nothing was printed)Expected console output
{ name: 'A' } { name: 'C', nickname: 'Cee' }Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Add an unknown property to a fresh literal
In the second example, line 2 adds z: 3 to an object that must be a Point.
Break #2
Assign undefined to an exact optional property
In the third example, line 3 sets nickname: undefined.
Myth vs fact
Myth
An optional property email?: string is the same as email: string | undefined.
Fact
With email: string | undefined the key must be written, even if its value is undefined. With email?: string the key may be left out entirely. They differ in how objects are created and in checks like "email" in obj.
Myth
readonly stops the property from ever changing.
Fact
It only stops the compiler from accepting an assignment through that name. In the JavaScript that runs, nothing is frozen.
Myth
Extra properties are always an error.
Fact
Only in a fresh object literal. Through a variable they are accepted, because TypeScript compares shapes (Topic 3.5).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Excess property checking is a heuristic layered on top of structural assignability: freshness is a flag on the object literal's type that disappears once the literal is stored in a variable. Topic 16.1 shows the internals, including why spreading doesn't always trigger it.
- ▸
exactOptionalPropertyTypesapplies to every optional property, including the onesPartial<T>creates (Topic 10.1), so an update object typedPartial<User>also refuses an explicitundefined. It breaks code that writesobj.prop = undefinedto "unset" a property; the fix is todelete obj.propor widen the property type. It is not part ofstrict;tsc --initenables it. - ▸
Object types are compared structurally: only the properties present in the target matter. That is how two unrelated classes with the same fields are interchangeable, and the reason
readonlydoesn't affect assignability (a{ readonly x: number }is assignable to{ x: number }, a known soundness hole).
Remember this
- 1
The problem this solves: Objects are passed around with a missing field or a misspelt property name, and the code reads
undefinedinstead of failing early. An object type lists exactly which properties must exist. An object groups related values under names called properties:{ name: "Asha", age: 30 }has two properties,nameandage. Its type, an object type, is written the same way with types in place of values:{ name: string; age: number }. Separate the properties with;(or,), and write the whole thing wherever a type fits: on a variable, a parameter or a return value. - 2
The checker holds you to the list in three ways. Every listed property must be present, each must have the right type, and when you read
user.agethe checker knows it is anumber. Reading a property that isn't listed (user.nmae) isTS2339, which is how typos in property names get caught (Topic 0.1). - 3
A question mark after a property name makes it optional:
email?: stringmeans the property may be missing. Reading it givesstring | undefined, so you must deal with the missing case before using it (Topic 1.7). Optional is about *the property being absent*, which is not quite the same as a property that is present and set toundefined. A flag calledexactOptionalPropertyTypes(on in thetsc --initfile) keeps the two apart: with it,email?: stringaccepts a missing email or a string, but notemail: undefined. If you want to allow it, writeemail?: string | undefined. - 4
**
readonly** before a property name means code may read it but not assign to it:readonly id: number. Assigning isTS2540. It is the right tool for things like ids, which should be set once. Like every type feature it is checked only by the compiler and removed from the JavaScript, so it is a promise to your teammates and to yourself, not a lock (the example below shows the value really changing at runtime). It is also shallow: it protects the property, not the object inside it. - 5
Excess property checks. When you write an object literal directly where a type is expected, any property not in the type is an error:
{ x: 1, y: 2, z: 3 }assigned to{ x: number; y: number }fails withTS2353, because a strayzis probably a typo (colourinstead ofcolor). The check applies only to fresh object literals (an object written right there). Put the same object in a variable first and then pass the variable, and there is no error, because TypeScript compares shapes and an object with extra properties still has the properties you asked for (Topic 3.5). - 6
Object types nest: a property can itself be an object type (
address: { city: string }) or an array. Writing a long type inline several times is tiring and error-prone, so soon you will give it a name with a type alias or an interface (Phase 3). For now think of the inline form as the raw material those are made of.
Explain it without notes
How do you describe an object's shape in TypeScript, and what does the checker verify?
What is the difference between email?: string and email: string | undefined?
What is an excess property check and when does it not apply?
What does readonly on a property guarantee?
Practice
Write a function describe(user) where user has a required name (text), a required age (number) and an optional email (text). It returns name followed by the email, or no email when it is missing. It must compile.
Why does the second line below compile while the first doesn't? Fix the first line without changing the type.
``ts
const a: { id: number } = { id: 1, extra: true };
const obj = { id: 1, extra: true };
const b: { id: number } = obj;
Trade-offs
- ↔
Inline object types are quick for one-off parameters but repeat badly. Name a shape (Phase 3) when it appears twice or more.
- ↔
exactOptionalPropertyTypesis more accurate but can makePartial-style update code and some library types awkward. Teams that dislike the friction turn it off; the cost is that "missing" and "undefined" merge.
Done when you can
I can write an object type with required, optional and readonly properties.
I can predict what reading an optional property gives.
I can explain when the excess property check fires and when it does not.
I can explain what
exactOptionalPropertyTypeschanges.I can say what
readonlydoes not do at runtime.