Structural Typing & Assignability
In one line
Two types are compatible if their shapes are compatible — names are irrelevant — so a Dog is assignable to a Person if it happens to have the right properties.
What it is
This is the deepest design decision in the language and the source of most early confusion. In a nominal system (Java, C#), class Meters and class Feet are distinct because they are declared distinct. In TypeScript they are the same type if their members match. That is what makes the language work with JavaScript's object-literal culture, and it is why type safety in a domain sense needs branding rather than declaration.
Assignability is directional: a value is assignable to a target if it has at least the target's members. Extra properties are fine, which is what makes composition and duck typing pleasant.
The exception is excess property checking on fresh object literals. Assigning { name: 'x', typo: 1 } directly to a variable of type { name: string } errors, even though the same object stored in a variable first assigns cleanly. This is a deliberate heuristic to catch typos at the point of creation, not a change to the assignability rule — and knowing that explains the workaround where assigning through a variable silently succeeds.
Function compatibility has its own asymmetries. Parameters are checked bivariantly for method syntax and contravariantly under strictFunctionTypes for function-property syntax — meaning a handler taking a broader parameter is safely assignable where one taking a narrower parameter is not. Fewer parameters are always fine, which is why arr.map(x => x) works when the callback signature offers three. Return types are covariant.
Two structural consequences worth internalising. Optional versus missing differ under exactOptionalPropertyTypes: { a?: string } and { a: string | undefined } are not the same type. And private members make a class nominal — two classes with identical shapes are incompatible if either has a private field, which is the one nominal escape hatch built into the language.
Why it matters
Nearly every confusing assignability error resolves once you think in shapes: why an interface satisfies a type it never mentions, why an excess property errors in one position and not another, why a callback with fewer parameters is accepted.
It is also a standard interview question, usually phrased as "what's the difference between structural and nominal typing?", with the follow-up being how to get nominal behaviour when you need it.
Key points
- Compatibility is by shape, not by name — a type satisfies another it has never heard of.
- Extra properties are allowed; the target's members are a minimum, not an exact match.
- Excess property checking applies only to fresh object literals, which is why assigning via a variable bypasses it.
strictFunctionTypesmakes function-property parameters contravariant; method syntax stays bivariant.- A callback may declare fewer parameters than the signature provides.
exactOptionalPropertyTypesdistinguishes an absent property from one explicitly set toundefined.- A private class member makes the class nominal — the only built-in escape from structural comparison.