Execute Program

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 if to narrow a variable's type. That only works because TypeScript knows how to analyze the condition in our if.

  • TypeScript analyzes conditions for more than just narrowing. For example, if we try to use certain if conditions 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.Pass Icon
  • >
    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.Pass Icon
  • 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 1 and 0 can never hold the same value, because the type 1 can only hold the value 1 and the type 0 can only hold the value 0. 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.Pass Icon
  • 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'Pass Icon
  • >
    let s;
    if ('' + 'a' === 'b') {
    s = 'they are equal';
    } else {
    s = 'they are not equal';
    }
    s;
    Result:
    'they are not equal'Pass Icon
  • In the first example above, zero and one both have the type number. A number can equal another number, so the condition is allowed. TypeScript doesn't care that the actual values are 0 and 1; 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 a string back. The same is true for all operators: in 1 * 2, the numbers themselves have literal types, but the overall expression gives us a number. 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'. A string could 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!