Objects: type and interface
Object shapes, optional and readonly fields, Record, type vs interface.
Updated
What typing an object means
You describe the object's shape: which keys it has and what type each value is. TS then checks that every object you use matches the shape.
type Product = {
id: number
name: string
price: number
tags?: string[] // optional — may be missing
readonly sku: string // can't be changed after creation
}
const p: Product = { id: 1, name: 'Laptop', price: 4000, sku: 'LP-1' }
p.sku = 'X' // ✗ readonly
p.color // ✗ doesn't exist on Producttype vs interface — differences and when
Both describe the shape of an object. In 90% of cases they're interchangeable.
type |
interface |
|
|---|---|---|
| Objects | ✓ | ✓ |
Unions, tuples, primitives, functions ('a' | 'b') |
✓ | ✗ |
| Extending | type B = A & { x: number } |
interface B extends A { x: number } |
| Declaration merging (two declarations with the same name combine) | ✗ | ✓ |
| Error messages | sometimes longer | sometimes clearer |
A practical recommendation: type by default (works for everything), interface when you write a library others will extend or when the team prefers it. What matters is being consistent.
Optional and readonly properties
type User = {
id: number
email: string
nickname?: string // string | undefined
readonly createdAt: Date
}
function displayName(user: User) {
return user.nickname ?? user.email // TS forces you to handle the missing case
}Dictionaries: index signatures and Record
When the keys aren't known upfront:
type Prices = { [productId: string]: number }
type Prices2 = Record<string, number> // equivalent, more readable
const stock: Record<'us' | 'ca', number> = { us: 10, ca: 2 } // fixed keys from a unionNested and composed types
type Address = { city: string; zip: string }
type Customer = { name: string; address: Address }
type Timestamps = { createdAt: Date; updatedAt: Date }
type Post = { title: string } & Timestamps // an intersection: has everything from bothStructural typing
TS compares objects by shape, not by name: any object that has the required fields is accepted.
type Point = { x: number; y: number }
const p3 = { x: 1, y: 2, z: 3 }
const p: Point = p3 // ✓ it has x and y — the extra field doesn't matter(The exception: an object literal written directly with extra fields gives an error — the "excess property check" — to catch typos.)
Summary
- You type the object's shape;
?optional,readonlyunchangeable. typeby default;interfacefor extending / libraries.Record<K, V>for dictionaries;&for combining.