Everyday TypeScript: Impossible Conditions
Welcome to the Impossible Conditions lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
We've seen conditional narrowing with type guards, where we can use an
ifto narrow a variable's type. That only works because TypeScript knows how to analyze the condition in ourif.TypeScript analyzes conditions for more than just narrowing. For example, if we try to use certain
ifconditions that are always false, that's a type error.>
let s;if (1 === 0) {s = 'it worked';}s;Result:
type error: This comparison appears to be unintentional because the types '1' and '0' have no overlap.
>
let s;if ('a' === 'b') {s = 'it worked';}s;Result:
type error: This comparison appears to be unintentional because the types '"a"' and '"b"' have no overlap.
Those type error messages are nice and clear! They also give us a hint about what the TypeScript compiler is doing internally. The error messages talk about types, not values. We can conclude that the compiler isn't comparing the values themselves. Instead, it's comparing their types.
In the error message, TypeScript uses the word "overlap". Types overlap if they have at least one possible value in common. The literal types
1and0can never hold the same value, because the type1can only hold the value1and the type0can only hold the value0. Likewise for the literal types'a'and'b'.Literal types are the simplest example of this, but it also applies to more complex types. If the types can't possibly overlap, we'll get the same kind of type error.
>
type User = {username: string};const amir: User = {username: 'amir'};type Album = {albumName: string};const kindOfBlue: Album = {albumName: 'Kind of Blue'};let s;if (amir === kindOfBlue) {s = 'it worked';}s;Result:
type error: This comparison appears to be unintentional because the types 'User' and 'Album' have no overlap.
TypeScript can't stop us from ever writing an impossible condition. It only prevents conditions where the types themselves make the condition impossible.
Here are some examples of conditions that are clearly impossible, but are allowed because the types could theoretically hold equal values:
>
const zero: number = 0;const one: number = 1;let s;if (zero === one) {s = 'they are equal';} else {s = 'they are not equal';}s;Result:
'they are not equal'
>
let s;if ('' + 'a' === 'b') {s = 'they are equal';} else {s = 'they are not equal';}s;Result:
'they are not equal'
In the first example above,
zeroandoneboth have the typenumber. Anumbercan equal anothernumber, so the condition is allowed. TypeScript doesn't care that the actual values are0and1; it doesn't even consider that! Some compilers do deeper value analysis in that way, but TypeScript isn't one of them.The second example does
'' + 'a'. Those two strings have the literal types''and'a'. However, when using the+operator on strings, we always get astringback. The same is true for all operators: in1 * 2, the numbers themselves have literal types, but the overall expression gives us anumber. This is a design decision made by the TypeScript team: operators don't return literal types.As human programmers, we can see that
'' + 'a'will definitely give the value'a', but the compiler doesn't know that. On the other side of'' + 'a' === 'b', the expression'b'still has the literal type'b'. Astringcould theoretically be equal to the literal type'b', so the condition is allowed.These impossible conditions are another case of TypeScript preventing bugs for us. If we compare two values whose types can never be equal, that's a mistake that we want to know about!