Object types

Describe the shape of an object, and know which of the two ways to do it to reach for.

04of 14
≈ 12Min read
8Parts

interface or type

Use interface for an object that something implements or extends, and type for everything else — a union, a function type, a mapped type. Being consistent matters more than which you pick.

for an object, these are equivalent
interface Door {
  name: string;
  days: number;
}

type Door = {
  name: string;
  days: number;
};
interfacetype
Object shapesyesyes
Unions, tuples, primitivesnoyes
Extendingextends&
Reopening to add fieldsyes — declaration mergingno
In error messagesShows the nameOften expanded

Optional, readonly, and the difference from undefined

three things that look similar
interface Door {
  name: string;
  note?: string;              // may be absent
  price: number | undefined;  // must be present, may be undefined
  readonly id: string;        // cannot be reassigned after creation
}

const door: Door = { name: "Painted", price: undefined, id: "a" };
door.id = "b";                // Cannot assign to 'id' because it is a read-only property
readonly is shallow, and compile-time only

readonly items: string[] stops you replacing the array, not pushing to it — use readonly string[] for that. And nothing is frozen at runtime.

Excess property checks

A literal assigned directly is checked for surplus keys, because a key that is not in the type is nearly always a typo. Assign it to a variable first and the check does not apply, which is either a useful escape hatch or a trap, depending on the day.

the rule that surprises people once
interface Door { name: string; days: number }

const extra = { name: "Painted", days: 5, colour: "sage" };
const a: Door = extra;                                    // fine — it is a variable

const b: Door = { name: "Painted", days: 5, colour: "sage" };
//                                          ^ Object literal may only specify known properties

Extending and combining

two spellings of the same idea
interface Product { id: string; price: number }
interface Door extends Product { name: string; days: number }

type Product = { id: string; price: number };
type Door = Product & { name: string; days: number };

// and a union is the other direction entirely
type Item = Door | Frame;

Index signatures and Record

when the keys are not known in advance
interface Prices { [finish: string]: number }
const prices: Prices = { painted: 480, oiled: 520 };

type Prices = Record<string, number>;                  // the same thing
type ByFinish = Record<Finish, number>;                // every finish required

prices.anything;    // number — even though it is not there
An index signature lies about missing keys

prices.nonsense is typed number and is actually undefined. noUncheckedIndexedAccess fixes this by making every lookup number | undefined, which is correct and occasionally tiresome.

Nested and optional chains

describe it once, use it everywhere
interface Order {
  id: string;
  customer: { name: string; email?: string };
  items: Array<{ door: Door; quantity: number }>;
  placedAt: Date;
}

order.customer.email?.toLowerCase();     // optional chaining follows the type

Functions and methods in a type

Under strictFunctionTypes the property form is checked more strictly than the method shorthand — a historical compromise for the DOM. Prefer the property form in your own code.

both spellings, and the one to prefer
interface Store {
  add(door: Door): void;           // method shorthand
  remove: (id: string) => void;    // property with a function type
  readonly size: number;
}

Describing what you already have

That last pair is worth memorising: one array, used at runtime for the list and at compile time for the type, with no chance of the two drifting apart.

typeof, so the type follows the value
const defaults = { finish: "painted", days: 5, express: false };

type Defaults = typeof defaults;        // { finish: string; days: number; express: boolean }
type Finish = typeof defaults.finish;   // string

const FINISHES = ["painted", "oiled", "glazed"] as const;
type Finish = typeof FINISHES[number];  // "painted" | "oiled" | "glazed"

Practice