Detailed differences between type annotation `variable: type` and type assertion `expression as type` in TypeScript

I was wondering what is the exact difference between type annotation
variable: type and type assertion expression as type

Type declaration variable: type tells the compiler that the variable must always conform to the declared type. It is used by typechecker when a value is assigned to the variable (the value must be compatible with the declared type), and also whenever the variable is used (the declared type of the variable must be compatible with whatever way the variable is used in each particular place).

Type assertion overrides built-in type compatibility rules. It allows you to tell the compiler that you know that the value actually conforms to the type you give in the assertion, thus suppressing error message about type incompatibility. There are limits, however – you can’t just assert that variable has any type you want (BTW there is any type just for that). As you quoted in the question, for type assertion to work,

the assertion from type S to T succeeds if either S is a subtype of T or T is a subtype of S

It works exactly in that way in each example:

const foo3 = { n: '' } as Foo; // KO: "Property 's' is missing..."

Here two types: {n?: number, s: string} and {n: string} are checked for compatibility – if any of them can be converted to another. It can’t be done either way: in one way, {n: string} is missing non-optional s and n has the wrong type (must be number | undefined); in another way, {n?: number, s: string} has wrong type for n (must be string).

The complete error message is

Type '{ n: string; }' cannot be converted to type 'Foo'.
  Property 's' is missing in type '{ n: string; }'.

When reporting structural type incompatibility, the compiler chooses just one incompatible property to show in the error message – it could be any of the three incompatibilities mentioned above.


const foo4 = { n: '' as any } as Foo; // ok

Works because {n?: number, s: string} is compatible with {n: any}: the first one can be assigned to the second – any is compatible with anything, and s is just ignored (basically, a value is compatible with the type if it has all non-optional properties compatible with declared type)


const foo5 = { n: 1, x: 2 } as Foo;   // KO: "Property 's' is missing..."

{n: number, x: number} is not assignable to {n?: number, s: string}s is missing, as compiler says:

Type '{ n: number; x: number; }' cannot be converted to type 'Foo'.
   Property 's' is missing in type '{ n: number; x: number; }'.

const foo6 = { s: '', x: 2 } as Foo;  // ok

Works because {s: string, x: number} is assignable to {n?: number, s: string}: s is OK, missing n is OK because it’s declared as optional, extra x is ignored


const foo7 = { s: 1, x: 2 } as Foo;   // KO: "Types of property 's' are incompatible."

Type of s is incompatible:

Type '{ s: number; x: number; }' cannot be converted to type 'Foo'.
  Types of property 's' are incompatible.
    Type 'number' is not comparable to type 'string'.

Leave a Comment