Execute Program

Everyday TypeScript: Type Widening

Welcome to the Type Widening 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 many examples of type inference in past lessons. For example, if we assign true to a variable then its type is inferred as boolean.

  • >
    let aBool = true;
    aBool = false;
    aBool;
    Result:
    falsePass Icon
  • TypeScript allowed us to assign false to aBool, so we know that its type must be boolean rather than the literal type true. But why isn't its type true? We assigned true to the variable, so it seems like its inferred type should be true.

  • What we're seeing here is a "quality of life" feature in TypeScript. TypeScript helps us out by "widening" the inferred type in some situations, including this one. When we assign true or false to a variable, TypeScript widens its type to boolean, since that's usually what we want.

  • The same happens for strings: if we assign 'a' to a variable, TypeScript could infer it as the literal type 'a'. But in most cases we'll want the type to be string, so that's what TypeScript infers. Likewise for numbers: if we assign 5 to a variable, we probably want the inferred type to be number, so TypeScript does that for us.

  • >
    let aString = 'a';
    aString = 'b';
    aString;
    Result:
    'b'Pass Icon
  • >
    let aNumber = 5;
    aNumber = 6;
    aNumber;
    Result:
    6Pass Icon
  • Type widening is very convenient... most of the time. Unfortunately, there are some cases where it causes type errors. Here's an example where type widening causes a type error.

  • (You can write type error if an example will result in a type error.)

  • >
    type User = {
    kind: 'user'
    name: string
    };

    function userName(user: User) {
    return user.name;
    }

    const user = {
    kind: 'user',
    name: 'Amir',
    };

    userName(user);
    Result:
    type error: Argument of type '{ kind: string; name: string; }' is not assignable to parameter of type 'User'.
      Types of property 'kind' are incompatible.
        Type 'string' is not assignable to type '"user"'.Pass Icon
  • TypeScript widened our kind property's type from the literal string type 'user' to the more general type string. Unfortunately for us, that makes the user variable's overall type {kind: string, name: string}. We're trying to pass it as a User with a narrower type of {kind: 'user', name: string}. That's a type error.

  • There are two possible solutions here. One is to explicitly declare our variable as const user: User. That works, but can be inconvenient in more complex situations.

  • The other solution is as const. When we append as const to any value, TypeScript won't widen its type. For example, if we assign true as const to a variable, its type will be true. Trying to assign false later will cause a type error.

  • >
    let justTrue = true as const;
    justTrue = false;
    Result:
    type error: Type 'false' is not assignable to type 'true'.Pass Icon
  • We can apply as const to a single string, a whole object, or any other value. The most common as const use is on a single literal string. In the next example, we use it only on the user's name, giving it the literal string type 'Amir'. When we try to assign a different name, we'll get a type error.

  • >
    let user = {name: 'Amir' as const};
    user.name = 'Betty';
    user.name;
    Result:
    type error: Type '"Betty"' is not assignable to type '"Amir"'.Pass Icon
  • Here's a code problem:

    In this code, Amir has kind: 'user'. However, TypeScript automatically widens kind's type from 'user' to string, causing a type error. Modify the amir object to prevent the widening, allowing us to pass the amir object as an argument of type User.

    type User = {
    kind: 'user'
    name: string
    };

    function userName(user: User) {
    return user.name;
    }

    const amir = {
    kind: 'user' as const,
    name: 'Amir',
    };

    userName(amir);
    Goal:
    'Amir'
    Yours:
    'Amir'Pass Icon
  • Type widening and as const may seem esoteric at first glance, but they come up surprisingly often in real TypeScript code. They're especially common in code that uses many discriminated union types.