Type Compaibility in TypeScript is based on Structural Subtyping. Structural Typing is a way of relating types based solely on their members. This is in contrast with nominal typing. Consider the following code:

1
2
3
4
5
6
7
8
9
10
11
interface Named {
name: string
}

class Person {
name: string
}

let p: Named
// OK, because of structural typing
p = new Person()

In nominally-typed language like C# or Java, the equivalnet code would be error because the Person class does not explicitly describe itself as being an implementor of the Named interface.

TypeScript’s structural type system was designed based on how JavaScript code is typically written. Because JavaScript widely uses anonymous objects like function expressions and object literals, it’s much more natural to represent the kinds of relationship found in JavaScript libraries with a structural type system instead of a nominal one.

The basic rule for TypeScript’s structural type system is the x is compatible with y if y has at least the same members as x.

1
2
3
4
5
6
7
8
interface Named {
name: string;
}

let x: Named;
// y's inferred type is { name: string, location: string }
let y = { name: 'Alice', location: 'Seattle' }
x = y

To check whether y can be assigned to x, the compiler checks each property of x to find a corresponding compatible property in y. In this case, y must have a member called name that is a string, so the assignment is allowed.

The same rule for assignment is used when checking function call arguments

The Type Compatibility in Variable Assignment and Function Arguments.

Comparing Two Functions

1
2
3
4
5
let x = (a: number) => 0
let y = (b: number, s: string) => 0

y = x // OK
x = y // Error

The check if x is assignable to y, we first look at the parameter list. Each parameter in x must have a corresponding parameter in y with a compabile type.

So x is assignable to y, but y is not assignable to x

Now let’s look at how return types are treated, using two functions that differs only by their return type:

1
2
3
4
5
let x = () => ({ name: 'Alice' })
let y = () => ({ name: 'Alice', location: 'Seattle' })

x = y // OK
y = x // Error

The type system enforces that the source function’s return type be a subtype of the target type’s return type.