05 — Structural Typing: Shapes, Not Names
"TypeScript checks whether the names match" was my compatibility model, and it predicted the wrong errors. Writing it down corrected the model entirely: TypeScript is structurally typed. Two types are considered compatible if they have the same shape, even if they were declared with completely different names. [1] What matters is the structure — which properties exist and what their types are — not the label on the type.
This is the single biggest philosophical difference between TypeScript and languages like Java or C#, which are nominally typed. In a nominally typed language, a Point class and a Coordinate class with identical fields are incompatible — the names differ. In TypeScript, they're interchangeable [1]:
interface Point { x: number; y: number; }
interface Coordinate { x: number; y: number; }
const p: Point = { x: 10, y: 20 };
const c: Coordinate = p; // perfectly validDespite the different names, p assigns to c without complaint. The compiler looked at the structure — both have x: number and y: number — and concluded they're compatible. The names are decorative.
Why structural typing fits JavaScript
The choice isn't accidental. JavaScript is full of anonymous object literals — { x: 10, y: 20 } — created on the fly and passed around. A nominally typed system would demand that every such object declare an explicit class, which fights how JavaScript is written. Structural typing meets the language where it is: an object is shaped like a Point, so it is a Point, no ceremony required. This is also why adopting TypeScript is gradual — a plain object literal from JavaScript code already satisfies any matching interface.
The rule, more precisely
The compiler walks the properties. For type A to be assignable to type B, every property required by B must be present in A with a compatible type. Note the directionality: A can have extra properties that B doesn't mention, and that's fine — B only asks for its own properties to be satisfied. (There's a subtlety with fresh object literals, where excess properties trigger an error, but for already-typed variables the "extra is fine" rule holds.)
This is why a function that accepts { x: number; y: number } will happily take a User object that happens to also have x and y among its fields. The function only cares about the shape it uses.
The footgun: accidental compatibility
The flip side of "shape is all that matters" is that two types can be accidentally compatible. An object with more fields than required still satisfies a narrower type, which is usually fine — but it means a typo or an unexpected field won't always be caught at the assignment site. The compiler trusts the shape, so when shapes coincidentally overlap, so does compatibility. This is the trade-off for the flexibility, and it's why precise types (literal types, branded types, discriminated unions) become valuable as a codebase grows.
How I use this
The practical upshot is that I design types around shapes, not hierarchies. I don't force unrelated types to share a base class or interface just so a function can accept them — if they have the right structure, the function takes them, and that's idiomatic. When I want to prevent accidental compatibility (say, two ID types that are both string but shouldn't be mixed), I reach for branding or nominal patterns, because plain structure won't distinguish them. The mental habit is to ask "what shape does this function need?" rather than "what type name should this be?" — and that shift is, for me, the whole point of TypeScript's design.
References
[1] Microsoft, "Type Compatibility," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/type-compatibility.html
[2] Microsoft, "Advanced Topics in Type Compatibility," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/type-compatibility.html#advanced-topics
Knowledge check · Question 1 of 5
TypeScript decides whether type A is assignable to type B by…
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!