Execute Program

TypeScript Basics: Conditional Narrowing

Welcome to the Conditional Narrowing lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • At first glance, union types don't seem very useful. If we have a variable of type User | User[], there's not much we can do with it. We can't treat it like a single User because it might be a User[]. But we also can't treat it like a User[] because it might be a single User.

  • We get around this by handling both possibilities in our code. For example: if we're passed one user, we return their name. If we're passed an array of users, we return its length.

  • >
    type User = {
    name: string
    };

    function nameOrLength(userOrUsers: User | User[]) {
    if (Array.isArray(userOrUsers)) {
    // Inside this side of the if, userOrUsers' type is User[].
    return userOrUsers.length;
    } else {
    // Inside this side of the if, userOrUsers' type is User.
    return userOrUsers.name;
    }
    }
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    nameOrLength({name: 'Amir'});
    Result:
    'Amir'Pass Icon
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    nameOrLength([{name: 'Amir'}, {name: 'Betty'}]);
    Result:
    2Pass Icon
  • userOrUsers is a User | User[]. Why are we allowed to access its length and its name here? Arrays don't have a name, and Users don't have a length.

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    const userOrUsers: User | User[] = [{name: 'Amir'}];
    userOrUsers.name;
    Result:
    type error: Property 'name' does not exist on type 'User[]'.Pass Icon
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    const userOrUsers: User | User[] = {name: 'Amir'};
    userOrUsers.length;
    Result:
    type error: Property 'length' does not exist on type 'User'.Pass Icon
  • The answer is that TypeScript can see our if (Array.isArray(userOrUsers)) check. If Array.isArray is true, then the type can only be User[], not User. The TypeScript compiler knows what Array.isArray means when used in a conditional.

  • The same logic applies to "else" side of the conditional. On that side, Array.isArray(userOrUsers) is false, so it must be a single User, not a User[].

  • In both cases, we say that we've narrowed the type. We've taken a wide type like User | User[] and reduced it to a narrower type like User or User[].

  • The Array.isArray function is a type guard. When used inside an if or other conditional, a type guard makes a guarantee about the type. It "guards" the code inside the conditional, ensuring that it only executes for certain types.