Skip to content

Structural Typing

Many languages (Java, C#) use nominal typing: two types are compatible only if they share a declared name or inheritance link. TypeScript uses structural typing: two types are compatible if their shapes match, regardless of names.

interface Point { x: number; y: number; }
interface Coord { x: number; y: number; }
let p: Point = { x: 1, y: 2 };
let c: Coord = p; // ✅ different names, identical shape — compatible

Point and Coord were never declared to be related, but TypeScript doesn’t care. A value that has an x: number and a y: number satisfies both. This is sometimes called “duck typing” — if it walks like a Point and quacks like a Point, it is a Point, as far as the type checker is concerned.

More properties is fine — it’s a subset relationship

Section titled “More properties is fine — it’s a subset relationship”

A value can have extra properties and still be assignable to a type that asks for fewer. This follows directly from the sets model: the set of “objects with x, y, and z” is a subset of “objects with x, y.”

interface Named { name: string; }
const user = { name: "Ada", age: 36 };
const n: Named = user; // ✅ user has name (plus extra) — that's fine

user has everything Named requires (a name), so it fits. The extra age is invisible to anyone holding it as a Named.

flowchart LR
  val["value:
{ name, age, email }"] -->|has everything
Named requires| target["target type:
Named { name }"]
  target -->|assignable ✅| ok["accepted"]
Structural compatibility is about required shape

The exception: excess property checks on object literals

Section titled “The exception: excess property checks on object literals”

There’s one place TypeScript tightens up. When you assign an object literal directly, it flags properties the target doesn’t know about — because a literal with unexpected keys is almost always a typo or mistake.

interface Options { width: number; }
const a: Options = { width: 10, height: 20 };
// ❌ Error: 'height' does not exist in type 'Options'.
const raw = { width: 10, height: 20 };
const b: Options = raw; // ✅ no error — not a fresh literal

Assign the same object through a variable and it’s allowed, because now it’s the ordinary structural rule (extra properties are fine). Excess property checking is a deliberate, narrow safety net for the common “I typed the option name wrong” bug — not a change to the underlying model.

Structural typing is why TypeScript feels so fluid with plain objects and why you rarely need to declare that one type “implements” another. It’s also why two libraries with their own Point types just work together. The cost is that unrelated types with the same shape are interchangeable — occasionally you want nominal distinctness (a UserId that isn’t just any string), which you fake with branded types (covered later in Practical Mastery).

What does "structural typing" mean?
Can a value with extra properties be assigned to a type that requires fewer?
Why does assigning `{ width: 10, height: 20 }` directly to `Options { width: number }` error, but assigning it via a variable does not?
What technique gives you nominal-style distinctness in a structural system?